Skip to content

fix(ci): make the cross-repo closer accept Fixes: like the duplicate-fix guard - #10393

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-9755-closing-keyword-parser-parity
Aug 20, 2026
Merged

fix(ci): make the cross-repo closer accept Fixes: like the duplicate-fix guard#10393
os-zhuang merged 1 commit into
mainfrom
claude/issue-9755-closing-keyword-parser-parity

Conversation

@claude

@claude claude Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes #9755

Two of this repo's closing-keyword parsers disagreed about the optional colon. duplicate-fix-guard.yml accepted it; cross-repo-issue-closer.yml did not. A merged PR written Fixes: objectstack-ai/objectui#456 therefore took the closer's exit path 1No cross-repository closing keywords in this PR body. — the same quiet green a body with no cross-repo reference at all produces. The foreign issue is lost with no notice on the PR and no annotation anywhere.

Which parser was right — measured, not inherited

The card's real question was which spelling matches GitHub, because if GitHub rejected the colon then the guard was over-matching and the edit pointed the other way. The guard's comment asserted the colon is GitHub syntax, and a comment is not evidence.

It is true, and the reference is in this repository — found by scanning 1000 closed PR bodies for a colon-form keyword, which returned exactly one specimen:

fact value
PR #10241 merged 2026-08-20T15:10:06Z
its body carried a not fixed: sentence bound to issue #10240 (verbatim below)
issue #10240 closed 2026-08-20T15:10:08Z, state_reason: completed
that issue's closing-link summary names #10241, total_count: 1
Filed, not fixed: #10240 — the same leak through the **delete** verb.

Quoted inside a fence deliberately: GitHub's parser does not fire in fenced blocks (measured here in #8476 step 1), so this citation cannot re-perform the close it documents. Do not lift that line into prose.

Two seconds after the merge, through the colon. So GitHub's parser binds it, the closer was under-matching, and the guard was already right. (The same specimen shows the surrounding prose changes nothing — the sentence said the issue was not fixed and it was closed anyway. That has a live consequence and a cost, both filed; see below.)

The change

cross-repo-issue-closer.yml gains :?, making its keyword-and-separator prefix identical to the guard's. Its reference scope is untouched — qualified owner/repo#N only, because the bare form is GitHub's own job.

before  \b(?:${KEYWORDS})\s+([\w.-]+)\/([\w.-]+)#(\d+)\b
after   \b(?:${KEYWORDS}):?\s+([\w.-]+)\/([\w.-]+)#(\d+)\b

One judgement, and why it is still spelled three times

⭐ The durable fix is one shared spelling, and a shared module is genuinely unavailable here: neither workflow checks the repo out. The closer deliberately never does (pull_request_target, and its header makes the no-checkout posture an invariant), and the duplicate-fix guard has no checkout step at all — its whole job is one github-script step. require() of a repo file resolves to nothing in either. Buying the import by giving the closer a checkout would spend its stated security posture for ten characters of regex.

So scripts/check-closing-keyword-parity.mjs (new) makes the judgement one instead of the text. It extracts all three parsers from the shipped bytes and asserts they agree behaviourally — same keyword set, same separator — rather than asserting three regexes are one string, which they legitimately are not. What differs on purpose (reference scope) is asserted too, with the colon present as well as without, so widening the separator cannot quietly widen the scope.

The sweep found a third parser

The card named two. A sweep of all 6119 tracked files for the grammar's two signatures found three parsers plus one non-parser:

id file scope
cross-repo-issue-closer .github/workflows/cross-repo-issue-closer.yml qualified
duplicate-fix-guard .github/workflows/duplicate-fix-guard.yml same-repo
h7-partof-closing-keyword closingKeywordRe() in scripts/pm/check-half-states.mjs bare
(not a parser) scripts/check-cross-repo-closer-outcome.mjs mutation anchors

The third — the predicate behind partof-closing-keyword-guard.yml — already accepted the colon, so it corroborates the direction 2-of-3 rather than changing the fix. The sweep is now mechanical: an unregistered fourth parser fails the gate, because "fix these two" is exactly the change that would miss one.

The existing pin was blind to this

Adding :? moved real behaviour and check-cross-repo-closer-outcome.mjs stayed green at all 105 assertions — no scenario had ever put a colon in front of a qualified reference. So the fixture now carries it: MIXED_BODY's middle target is reached through CLOSES:, which makes the colon load-bearing for that body's target arity, and M15 restores the pre-fix text and drives P1 red.

Reverse verification

Reverted the closer to origin/main on a committed tree, re-ran, restored, confirmed byte-identical (git status clean).

reverted restored
check-closing-keyword-parity exit 1, 9 failures, all cross-repo-issue-closer does not accept the OPTIONAL-COLON spelling (one per keyword) exit 0
check-cross-repo-closer-outcome exit 1, 29 failures; P1 finds exactly the three qualified foreign targets, got 2 exit 0

Predicted direction was RED on both, and that is what happened.

Gates — all at 08a11b510, the pushed head

Derived with node scripts/pm/dispatch-gates.mjs (no args, from the merge base), which self-discovered the new gate via its lint.yml registration. Verdict lines as each gate printed them:

check-nul-bytes: OK (scanned 6115 text file(s) ... no raw ASCII control bytes).
OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
check-node-version: OK (29 setup-node step(s) across 26 workflow(s), all on Node 22).
check-required-contexts: OK
check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).
check-type-check-coverage: OK — 64/77 workspace packages type-checked
check-workflow-status-functions: OK (scanned 26 workflow file(s), 45 job(s) ...)
check-cross-repo-closer-outcome --self-test: 81 assertions, 15 mutations ... each driven to red.
check-cross-repo-closer-outcome: OK (107 assertions over 18 scenarios ...)
check-closing-keyword-parity --self-test: 24 assertions, 5 mutations ... each driven to red.
check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators;
  sweep found 5 file(s) carrying the grammar across 6120 tracked file(s), all registered).
eslint (changed scripts only): clean

Declared narrowing (one): check:type-check-debt was derived — only because this PR edits lint.yml — and refuses to run without a fully built package closure, by its own guard against measuring a different world. This diff contains zero TypeScript, so its ledger cannot move; CI runs it with the closure built. Its sibling check:type-check-coverage, same script and same self-test, ran green above.

Declared lock usage: every gate above is a pure source-text scanner that neither builds nor reads dist/, so they ran outside os-verify-lock.sh. Nothing here built anything.

Clause ②: not triggered. The path limb is packages/spec/src/** and this PR touches none of it; no public/authorable surface changes, no accept/reject contract widens — the closer's recognition of an already-valid GitHub spelling narrows nothing and widens no schema.

#9465 epic fence: checked, as the triage comment on the card asked. The fence is .changeset/**, the changeset scripts, cut-rc.yml, release.yml, pr-automation.yml's changeset steps and root package.json. This PR touches none of them — the new gate is registered in lint.yml as node scripts/... rather than a pnpm check:* alias precisely because that alias would land in root package.json, following the precedent the adjacent step already documents. So the card is neither fenced nor blocked.

Changeset

None — skip-changeset. Judged by publish surface, not habit: the diff is two workflow files and two repo-root scripts/*.mjs gates. No packages/* file is touched, so nothing publishes.

Filed, not folded in


Generated by Claude Code

…e-fix guard

The repo parses GitHub's closing-keyword grammar in three places and two of
them disagreed about the optional colon. `duplicate-fix-guard.yml` accepted it,
`cross-repo-issue-closer.yml` did not, so a merged PR written
`Fixes: objectstack-ai/objectui#456` took the closer's exit path 1 -- the same
quiet green a body with no cross-repo reference at all produces -- and the
foreign issue was lost with no notice on the PR and no annotation anywhere.

The guard's comment claimed the colon is GitHub syntax; that claim decided the
direction of the fix and was verified rather than inherited. It is true, and
the reference is in this repository: PR #10241 merged 2026-08-20T15:10:06Z
carrying a `not fixed:` sentence bound to issue #10240, and that issue closed
as `completed` at 15:10:08Z with its own closing-link summary naming #10241
alone. So the closer was under-matching and is widened; the guard was already
right.

Also adds the durable half. A shared module is unavailable -- neither workflow
checks the repo out -- so `scripts/check-closing-keyword-parity.mjs` extracts
all three parsers from the shipped bytes and asserts they agree behaviourally
on the keyword set and the separator, while still refusing what each is scoped
to refuse, and sweeps every tracked file for an unregistered fourth.

The existing closer pin was blind to this: adding `:?` moved real behaviour and
all 105 assertions stayed green, because no scenario had ever put a colon in
front of a qualified reference. P1 now carries the spelling and M15 restores
the defect.

Fixes #9755
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 20, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 20, 2026 17:52
@os-zhuang
os-zhuang enabled auto-merge August 20, 2026 17:53
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 20, 2026
Merged via the queue into main with commit e502a6a Aug 20, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-9755-closing-keyword-parser-parity branch August 20, 2026 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The repo's two closing-keyword parsers disagree about Fixes: — duplicate-fix-guard accepts the colon, cross-repo-issue-closer silently does not

2 participants