ci(duplicate-fix-guard): delimit the branch-name match so a longer card number cannot prefix-match - #18999
Conversation
…rd number cannot prefix-match
The branch-name advisory asked `branch.includes(`issue-${n}`)`, an undelimited
substring test, so a declared card number that is a PREFIX of the number in the
branch name satisfied it: declaring card 186 from `claude/issue-18611-x` kept the
advisory silent, which is exactly the case it exists to warn about — a fix branch
naming a DIFFERENT card. The match now carries the same `(-|$)` alternation the
documented pre-check uses, and the :79 comment restates that pre-check in that
spelling. Advisory semantics are unchanged: still `core.warning`, never red.
Probe of the predicate lifted verbatim out of the file, before -> after:
`claude/issue-18611-x` warns false -> true; `claude/issue-186-real`,
`claude/issue-186`, `feat/x`, `claude/issue-1186-x` and `claude/issue-0186-x`
unchanged.
Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Landing record — every pre-check met; a workflow file lands only by a human merge (skills seat, Every check on Generated by Claude Code |
Fixes #18922
Clause-②: no
The branch-name advisory in
.github/workflows/duplicate-fix-guard.ymltested the declaredcard number as an undelimited substring of the branch name, so a declared number that is a
PREFIX of the number in the branch satisfied it. The advisory then stayed silent in exactly the
case it exists to warn about: a fix PR whose branch names a DIFFERENT, longer card. The matcher
now carries the same
(-|$)alternation the documented claim pre-check uses (PR #18918, inflight on
AGENTS.mdrule 2), and the comment above it restates that pre-check in the samespelling.
Advisory semantics are unchanged: still
core.warning, never red — existing branches must notgo red retroactively, which is the file's own stated contract. Nothing else in the workflow
moves: the closing-keyword pattern,
declaredIssues, the open-PR scan and the first-comefirst-served red are byte-identical.
What moved — one file, two places
The matcher (was line 85, now line 91):
NOTabove stands for the JavaScript logical-not operator, present in the file on both sides: GitHub's write-side sanitizer deletes that character when it immediately precedes a left square bracket (platform-readings.md:332–:333, fences and inline code included), so it is written out in words here.The comment above it (was line 81) restated the pre-check as a bare
git ls-remote | grepofthe number; it now spells it as
AGENTS.mdrule 2 does —git ls-remote --heads origin, pipedinto
grep -Eof the card number followed by the(-|$)alternation — and adds why thedelimiter is load-bearing on BOTH sides: the right-hand alternation is what stops the prefix
match, its
$arm is what keeps the slug-less branch spelling matching, and the literalissue-on the left is what keeps a longer number that merely ENDS in the declared one frommatching. (The placeholder inside the file keeps the angle-bracket spelling
AGENTS.mduses;it is written here without them on purpose, because this body is sanitized.)
Probe — before / after
A workflow's inline
github-scriptbody is not reachable from any unit test in this repo, sothe evidence is the predicate itself, lifted verbatim out of the file: the probe locates the
single
[...mine].some(line, strips only theif (and) {wrapper, and evaluates what isleft. Nothing is retyped, and the same probe ran against the same path before and after the
commit.
minepr.head.ref{186}claude/issue-18611-xfalsetrue{186}claude/issue-186-realfalsefalse{186}claude/issue-186falsefalse{186}feat/xtruetrue{186}claude/issue-1186-xtruetrueissue-{186}claude/issue-0186-xtruetrueExactly one row moves, in the one direction predicted before the run. Rows 3 and 4 pin both arms
of the alternation: a bare trailing dash would break row 3, and a bare
$would break row 2.The file also still parses:
yaml.parseon the workflow, then the inline script through theAsyncFunctionconstructor (the shapeactions/github-scriptwraps it in) — both OK, 4594characters of script body.
Reader test
A PR declaring card 186 whose branch is
claude/issue-18611-xnow gets the branch-name warning;claude/issue-186-realstill does not.Gates
Derived, not recalled:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackfrom this worktree (change set: 1 path, three-dot vs merge based8b12fca9; identical list before and after agit fetch origin main). 36 commands; each runin the foreground, exit code captured by redirect-then-
$?, never across a pipe. 35 exited 0.The 36th is
pnpm check:pm-dispatch-gates, whose own file header prescribes the detached formon an agent container ("Do not run
pnpm check:pm-dispatch-gatesin the foreground there.Detach it and poll the log instead"): its
--self-testhalf exited 0, and the bare battery isrunning detached at the time this PR is opened, with another agent's copy of the same battery
contending for the box. Its final reading is recorded in the
os-dev-reportcomment on #18922rather than guessed at here —
--ranreconciliation over the other 35 reports 0 NOT-MEASUREDand no other family unrun.
Outside that derived set, and not claimed as cleared here: the 53 artifact-roster families
(seven of which keep their roster under
.github), the 11 declared-wide families and thealways-runs tail — CI runs them. This diff adds no workflow file, so no roster gains a member.
skip-changeset, measured70 non-private workspace packages, every one of them with a
files[]array; zero entriesmention
.github, and the changed path lives at the repo root, outside every package directoryand therefore inside no package tarball. Positive control:
packages/spec'sfiles[]namesreal published paths (
dist,json-schema,api-surface, …). Nothing published moves ⇒Clause-②: no,skip-changeset.Acceptance notes
platform-readings.md:332–:333), fences and inline code included. The file has the operator; the card was hit by the platform, not mis-transcribed by its author. Repaired by the reviewing seat by spelling the operator in words (NOT), the readings' prescribed author-side form. Noted, not filed.this branch's base
AGENTS.mdrule 2 still carries the undelimitedgrep. The delimitedspelling this PR writes into the workflow comment is the one that PR will land, byte-for-byte
(
git ls-remote --heads originpiped intogrep -Eof the number plus the alternation). Ifthat PR never lands, this comment is the more correct of the two spellings and
AGENTS.mdisthe one that drifts. Noted, not filed.
Landing
.github/workflows/**is not a governed surface —node scripts/pm/check-governed-merges.mjs --test .github/workflows/duplicate-fix-guard.ymlanswers "NOT governed, 0 of 1 path(s) hit theregister". It is still not a PR an agent seat can land: the seats' auto-merge answers 422 on
workflow files, so this is left as a draft for a human merge. No label beyond
skip-changesetis set by the author seat.
Generated by Claude Code
Generated by Claude Code