Skip to content

fix(lint): the partof gate stops asking for a rewrite it also forbids - #16749

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-16653-partof-guidance-pushed-branch
Sep 8, 2026
Merged

fix(lint): the partof gate stops asking for a rewrite it also forbids#16749
hotlong merged 1 commit into
mainfrom
claude/issue-16653-partof-guidance-pushed-branch

Conversation

@hotlong

@hotlong hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

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, no REQUIRED_CONTEXTS change, 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:

  • Branch not pushed yet — reword the commit messages and push once. Nothing published is rewritten and the check goes green. This was always the reachable remedy; it was never scoped to the case where it applies.
  • Branch already pushed — no author action clears the red, stated plainly, with the reason (the gate reads the PR's commit list, so a commit on top joins that list) and three measured facts that let a reader stop working on it.

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:

$ git log -1 --format=%B 0a61db1f5 | sed -n '1p;35p'
fix(tooling): isolate git children from ambient GIT_* and make a shared core.bare flip loud (#16646)
Refs #16624

The landed message is the branch commit's verbatim, carrying its Refs trailer; the body's closing keyword appears nowhere in it. This confirms the filer's addendum (5573612912) independently. ⚠️ It does not weaken RULE 2 — it is RULE 2's premise: a trailer left on a pushed commit really does reach permanent history.

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 no merge_group event 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 of and Refs — 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:

leg commit message before after
A as landed (carries Refs #16624) exit 1, names 0a61db1f5 exit 1, names 0a61db1f5
B same message, that one line removed exit 0 exit 0

The ::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 d5167f42e4689d98) and the restore leg proven byte-identical to HEAD (e4689d98, git diff HEAD empty, self-test green again). ⚠️ 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.

Verification

  • pnpm check:partof-closing-keyword (this script's --self-test): 89 cases pass, exit 0.
  • All 31 derived gates run, all exit 0. Derived in-worktree with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (1 path vs merge base 1ea349f0e, three-dot), and reconciled: ✓ dispatch-gates --ran: 31 derived famil(ies) accounted for — 31 run, 0 NOT-MEASURED.
  • eslint . --no-inline-config (the whole pnpm lint set, not a narrowing): exit 0 over 6346 files, 0 errors / 0 warnings, at 1cbd8d45.
  • No package build or package test is owed: repo-root scripts/ is inside no workspace package, and no *.test.* in the tree names this script — its suite is its --self-test.
  • Mergeability read against origin/main 1ea349f0 from a driver-free bare probe: clean, no conflicting paths.

skip-changeset

Nothing publishes. The root package is private, and across all 70 published packages every files[] glob is package-local — none escapes its own directory, so repo-root scripts/ cannot ship. Positive control: 70 of 70 published packages declare a non-empty files[] (sample ["dist","README.md","CHANGELOG.md"]), so the scan read what it claims to have read.

Acceptance notes


Generated by Claude Code

`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
@github-actions github-actions Bot added the size/m label Sep 8, 2026
@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026 — with Claude
@hotlong
hotlong marked this pull request as ready for review September 8, 2026 03:51
@hotlong
hotlong enabled auto-merge September 8, 2026 03:51
@hotlong
hotlong added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit cf33adb Sep 8, 2026
38 of 39 checks passed
@hotlong
hotlong deleted the claude/issue-16653-partof-guidance-pushed-branch branch September 8, 2026 04:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants