Skip to content

[finding] check-partof-closing-keyword RULE 2 is enforced at PR time but only repairable by a force-push the repo forbids #17253

Description

@claude

Filed by the domain:engine execution PM seat, from the #16224 round (PR #17252).

⛔ Filed unassigned and unlabelled, as an observation for triage. Grade deliberately not asserted, domain deliberately not asserted.

The gap

check-partof-closing-keyword.mjs RULE 2 forbids a card-relation trailer in any commit message — 「No commit on the pull request may carry a card-relation trailer at all: no closing keyword, no Part-of and no Refs bound to any card number. The body is the only carrier of the relation.」 The same contract is stated to implementers at .claude/agents/os-dev.md:283: 「卡片关系只在 PR 正文声明一次:commit ⛔ 不带卡片 trailer」.

The rule is enforced at PR time by a check that cannot repair what it catches. By the time the guard runs, the commits are pushed. The only edit that removes a trailer from a pushed commit message is an amend/rebase/force-push — which this repository forbids absolutely:

  • AGENTS.md:470 (Prime Directive): 「Never git push --force / --force-with-lease, and never push main.」
  • .claude/agents/os-dev.md:68: 「⛔ 永不编辑 content/docs/releases/、force-push、推 main、合并任何东西。」

⇒ A dev that writes the trailer has produced a state that is red and unrepairable at the same time. The check is advisory (partof-closing-keyword-guard.yml:27-30 — no merge_group: trigger, deliberately out of the required-context registry), so the PR still lands; the trailer lands with it.

Measured on PR #17252

All three commits carried it:

ab046d7c25  "Red on this commit, by construction. Part of #16224."
a05c0449e9  "Part of #16224."
179657d5f7  "Part of #16224."

node scripts/check-partof-closing-keyword.mjs ⇒ exit 1, on a real finding. The implementer found it itself and declared it unprompted rather than leaving it for a reviewer — the process worked at every step except the one that would have prevented it.

Why the usual escape does not apply here

The guard's own workflow documents a no-push repair for its OTHER rules: 「rewording the sentence fires a fresh event carrying the fresh body and the check goes green with no push and no re-run」 (partof-closing-keyword-guard.yml:21-26). That works because RULE 1 and RULE 3 read the body, which is editable in place. RULE 2 reads commit messages, which are not. The edited trigger buys RULE 2 nothing.

Nor can it be discharged at the merge button: this repo lands through the merge queue, and a queue merge does not let a lander retype the assembled squash message.

What a fix might look like — options only, not a recommendation

Whoever picks this up should expect to choose, not to implement a foregone conclusion:

  1. Move the enforcement earlier — a commit-msg hook in .claude/hooks/ refusing the trailer at write time, where the repair is free. Cost: hooks are a governed surface, so it is not a seat-landable change.
  2. Make the check's failure text state the accepted outcome, since the state is unrepairable by design. Today it fails without saying that the only lawful response is to land with the residue, which invites exactly the force-push the repo forbids — this seat itself briefly instructed a dev to do that before re-reading the rules.
  3. Decide the residue is acceptable and downgrade RULE 2 to report-only, on the argument that Part of moves no card and the body remains the operative carrier.
  4. Do nothing — a legitimate answer if the trailer's cost is judged lower than any of the above. If so this closes not_planned with that reasoning recorded.

Not to be conflated with

RULE 1 (a body that says both Part of #N and closes #N) and RULE 3 (a closing keyword inside a negating sentence) are body-scoped, repairable in place, and working as intended. This card is about RULE 2 only.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions