fix(lint): the partof gate stops asking for a rewrite it also forbids - #16749
Merged
Conversation
`check-partof-closing-keyword`'s RULE 2 guidance told the author to push reworded commits while forbidding amend, rebase and force-push in the same breath. On an already-pushed branch those are the same act: the gate reads the PR's commit list, so a commit on top joins that list and leaves the offending message in it, and rewording a pushed commit is the rewrite AGENTS.md bans. The red was therefore permanent for that branch however the author responded, and a seat that read the text concluded an amend was wanted and instructed one — the dev's refusal is what stopped it, not the wording. The same text also had the merger "take the squash message from that body". Measured on 0a61db1, the squash of PR 16646: the landed message is the branch commit's verbatim and carries its Refs trailer, while the body's closing keyword appears nowhere in it. The squash message is assembled from the COMMITS. That does not weaken RULE 2 — it is RULE 2's premise. - The guidance now splits on timing. An UNPUSHED branch gets the reachable remedy (reword now, push once, green). An already-pushed branch is told plainly that no author action clears the red, with the three measured facts that let a reader stop working on it: the check run is advisory at the branch-protection layer, the squash takes the commits, and the card closes from the PR body's keyword regardless. - What a landed trailer costs is stated by spelling rather than flattened: Part-of and Refs land as a reference and move no card, a closing keyword lands on the surface the parser reads. The landing decision itself is handed to whoever lands the PR — this gate reports and does not order a merge. - Six self-test pins hold the repaired shape, and the RULE 2 battery floor rises 13 to 19 so they are floored rather than merely present. ⛔ No verdict logic, exit code or required-context change: on PR 16646's shape the gate still exits 1 naming that commit, and still exits 0 with the trailer line removed, byte-identically. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY
This was referenced Sep 8, 2026
hotlong
marked this pull request as ready for review
September 8, 2026 03:51
hotlong
enabled auto-merge
September 8, 2026 03:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #16653
check-partof-closing-keyword's RULE 2 guidance told the author to push reworded commits while forbidding amend, rebase and force-push in the same breath. On an already-pushed branch those are the same act, so the red was permanent for that branch however the author responded — and a PM seat that read the text concluded an amend was wanted and instructed one. The dev's refusal is what stopped it, not the wording.⭐ Keeping the card's own most accurate sentence, because it is what this PR does not change: the gate did its job — it caught a real contradiction between a commit and a PR body; only its advice about what to do next was unreachable.
What changed
Guidance text and its pins in
scripts/check-partof-closing-keyword.mjs. ⛔ No verdict logic, no exit code, noREQUIRED_CONTEXTSchange, and remedy 3 from the card (skipping a trailer that matches the body's keyword) is deliberately absent — that is gate logic and needs a census.The guidance now splits on timing, which is what dissolves the contradiction:
The measured facts the text now carries
1. The squash message is assembled from the COMMITS, not from the PR body. The old text had the merger "take the squash message from that body". Measured on
0a61db1f5, the squash of PR #16646:The landed message is the branch commit's verbatim, carrying its⚠️ It does not weaken RULE 2 — it is RULE 2's premise: a trailer left on a pushed commit really does reach permanent history.
Refstrailer; the body's closing keyword appears nowhere in it. This confirms the filer's addendum (5573612912) independently.2. The check run is advisory at the branch-protection layer. It is absent from the six entries of
REQUIRED_CONTEXTS(scripts/check-required-contexts.mjs), and its own workflow states the mechanism: it subscribes to nomerge_groupevent because a queue build carries no PR body to judge (.github/workflows/partof-closing-keyword-guard.yml).3. The card closes from the PR body's keyword. #16624 closed on that merge although no commit message named a closing keyword for it.
One place this PR departs from the dispatched wording, on measurement
The suggested line was that a landed trailer is cosmetic. Measured, that holds for
Part ofandRefs— they land as a reference and move no card — but not for a closing keyword, which lands on the surface GitHub's parser reads. Writing "cosmetic" flatly would have replaced one factually wrong remedy with another, so the text states the cost by spelling instead. That asymmetry is also the argument for why the rule refuses all three at PR time.⛔ The text does not tell anyone to merge. It hands the landing decision to whoever lands the PR, under the rules that bind them — a seat whose queue-entry rule requires every check green is not overruled by a gate's own output.
Acceptance — the verdict is unchanged
The card's acceptance is that on PR #16646's shape the verdict does not move. Reproduced from the real commit message and a body whose first line is that PR's closing keyword:
Refs #16624)0a61db1f50a61db1f5The
::error::annotation, the✗headline and the commit-naming opener are byte-identical before and after; leg B's entire output is byte-identical. Only guidance prose moved.Reverse verification
The six new pins are proven able to fail: restoring the old two-line tail block turns 5 of them red (
exit 1,5 of 89 case(s) failed), naming the unpushed scoping, the pushed-permanence sentence, the removed false squash claim, the measured replacement and the no-merge-order clause. The sixth guards the amend/rebase/force-push prohibition in the per-commit sentence, which that ablation does not touch — correctly green.The mutation was proven on disk before the run (printed-only anchor 1 → 0, old sentence 0 → 1, blob⚠️ The first ablation attempt anchored on a phrase the new pin also spells, so its count fell 2 → 1 instead of to 0 and the on-disk guard refused the reading; the mutation had landed and the expectation was wrong. Re-anchored on a printed-only phrase and re-run — recorded because a silent retry is the same defect one layer up.
d5167f42≠e4689d98) and the restore leg proven byte-identical toHEAD(e4689d98,git diff HEADempty, self-test green again).Verification
pnpm check:partof-closing-keyword(this script's--self-test): 89 cases pass, exit 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(1 path vs merge base1ea349f0e, three-dot), and reconciled:✓ dispatch-gates --ran: 31 derived famil(ies) accounted for — 31 run, 0 NOT-MEASURED.eslint . --no-inline-config(the wholepnpm lintset, not a narrowing): exit 0 over 6346 files, 0 errors / 0 warnings, at1cbd8d45.scripts/is inside no workspace package, and no*.test.*in the tree names this script — its suite is its--self-test.origin/main1ea349f0from a driver-free bare probe: clean, no conflicting paths.skip-changesetNothing publishes. The root package is
private, and across all 70 published packages everyfiles[]glob is package-local — none escapes its own directory, so repo-rootscripts/cannot ship. Positive control: 70 of 70 published packages declare a non-emptyfiles[](sample["dist","README.md","CHANGELOG.md"]), so the scan read what it claims to have read.Acceptance notes
check-partof-closing-keyword's RELATION_CONTRACT presents as a verbatim citation of the os-dev agent rules but carries a second sentence that file does not have #16747 —RELATION_CONTRACTpresents as a verbatim citation of.claude/agents/os-dev.mdbut carries a second sentence that file does not have; the sentence exists nowhere else in the tree. Not repaired here: one candidate fix edits a governed surface and the choice between "trim the quote" and "amend the ruling" is a decision, not a mechanical correction.check-partof-closing-keywordmust stop telling agents to force-push #16516 is not addressed here, and its title names this same gate ("must stop telling agents to force-push") beside a pm-dispatch queue-entry rule change. [Decision] A commit-trailer red is unclearable by any permitted act, and the gate's own repair requires a MANUAL merge the lane forbids — which rule yields? #16502 remains open and asks which rule yields when a commit-trailer red is unclearable — a decision card. This PR changes only the gate's text and answers neither; whether either is now partly overtaken by it is triage's read.Generated by Claude Code