Skip to content

Commit 9a2e715

Browse files
os-elon-muskclaude
andauthored
ci(duplicate-fix-guard): delimit the branch-name match so a longer card number cannot prefix-match (#18999)
Fixes #18922 Clause-②: no The branch-name advisory in `.github/workflows/duplicate-fix-guard.yml` tested the declared card 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, in flight on `AGENTS.md` rule 2), and the comment above it restates that pre-check in the same spelling. Advisory semantics are unchanged: still `core.warning`, never red — existing branches must not go 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-come first-served red are byte-identical. ## What moved — one file, two places The matcher (was line 85, now line 91): ```diff - if (NOT [...mine].some((n) => branch.includes(`issue-${n}`))) { + if (NOT [...mine].some((n) => new RegExp(`issue-${n}(-|$)`).test(branch))) { ``` `NOT` above 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 | grep` of the number; it now spells it as `AGENTS.md` rule 2 does — `git ls-remote --heads origin`, piped into `grep -E` of the card number followed by the `(-|$)` alternation — and adds why the delimiter 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 literal `issue-` on the left is what keeps a longer number that merely ENDS in the declared one from matching. (The placeholder inside the file keeps the angle-bracket spelling `AGENTS.md` uses; it is written here without them on purpose, because this body is sanitized.) ## Probe — before / after A workflow's inline `github-script` body is not reachable from any unit test in this repo, so the evidence is the predicate itself, **lifted verbatim out of the file**: the probe locates the single `[...mine].some(` line, strips only the `if (` and `) {` wrapper, and evaluates what is left. Nothing is retyped, and the same probe ran against the same path before and after the commit. | declared `mine` | `pr.head.ref` | warns BEFORE | warns AFTER | reading | | --- | --- | --- | --- | --- | | `{186}` | `claude/issue-18611-x` | `false` | `true` | **the defect** — the branch names card 18611, not 186 | | `{186}` | `claude/issue-186-real` | `false` | `false` | control — declared card, dash arm | | `{186}` | `claude/issue-186` | `false` | `false` | control — declared card, slug-less end-of-string arm | | `{186}` | `feat/x` | `true` | `true` | control — no card named | | `{186}` | `claude/issue-1186-x` | `true` | `true` | the declared number is a SUFFIX of the branch number — left-anchored by the literal `issue-` | | `{186}` | `claude/issue-0186-x` | `true` | `true` | leading zero — same anchor | Exactly 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.parse` on the workflow, then the inline script through the `AsyncFunction` constructor (the shape `actions/github-script` wraps it in) — both OK, 4594 characters of script body. ## Reader test A PR declaring card 186 whose branch is `claude/issue-18611-x` now gets the branch-name warning; `claude/issue-186-real` still does not. ## Gates Derived, not recalled: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` from this worktree (change set: 1 path, three-dot vs merge base `d8b12fca9`; identical list before and after a `git fetch origin main`). 36 commands; each run in 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 form on an agent container ("Do not run `pnpm check:pm-dispatch-gates` in the foreground there. Detach it and poll the log instead"): its `--self-test` half exited 0, and the bare battery is running 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-report` comment on #18922 rather than guessed at here — `--ran` reconciliation over the other 35 reports 0 NOT-MEASURED and 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 the always-runs tail — CI runs them. This diff adds no workflow file, so no roster gains a member. ## `skip-changeset`, measured 70 non-private workspace packages, every one of them with a `files[]` array; zero entries mention `.github`, and the changed path lives at the repo root, outside every package directory and therefore inside no package tarball. Positive control: `packages/spec`'s `files[]` names real published paths (`dist`, `json-schema`, `api-surface`, …). Nothing published moves ⇒ `Clause-②: no`, `skip-changeset`. ## Acceptance notes - The card's quoted snippet of line 85 shows the predicate without its leading logical-not, and so did this body as first stored: GitHub's write-side sanitizer deletes that operator when it immediately precedes a left square bracket (`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. - The premise's dating is off by one landing: PR #18918 is **open and draft**, not landed, so at this branch's base `AGENTS.md` rule 2 still carries the undelimited `grep`. The delimited spelling this PR writes into the workflow comment is the one that PR will land, byte-for-byte (`git ls-remote --heads origin` piped into `grep -E` of the number plus the alternation). If that PR never lands, this comment is the more correct of the two spellings and `AGENTS.md` is the 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.yml` answers "NOT governed, 0 of 1 path(s) hit the register". 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-changeset` is set by the author seat. --- _Generated by [Claude Code](https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF)_ --- _Generated by [Claude Code](https://claude.ai/code)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent ee99340 commit 9a2e715

1 file changed

Lines changed: 9 additions & 3 deletions

File tree

‎.github/workflows/duplicate-fix-guard.yml‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,17 @@ jobs:
7878
7979
// Branch-name convention (advisory, never red): a fix branch named
8080
// `claude/issue-<n>-<slug>` is discoverable by the next session
81-
// with one `git ls-remote | grep issue-<n>`. #4555 vs #4559
82-
// happened partly because the branches shared no token to grep.
81+
// with one `git ls-remote --heads origin | grep -E 'issue-<n>(-|$)'`.
82+
// #4555 vs #4559 happened partly because the branches shared no
83+
// token to grep. Both spellings delimit the number on the RIGHT:
84+
// undelimited, `issue-186` matches `claude/issue-18611-x`, so the
85+
// advisory stays silent for a branch naming a DIFFERENT card —
86+
// the false negative. The `$` arm keeps the slug-less spelling
87+
// `claude/issue-186` matching; `issue-` anchors the left, so
88+
// `issue-1186` and `issue-0186` do not match card 186 either.
8389
// Warning only — existing branches must not go red retroactively.
8490
const branch = pr.head.ref;
85-
if (![...mine].some((n) => branch.includes(`issue-${n}`))) {
91+
if (![...mine].some((n) => new RegExp(`issue-${n}(-|$)`).test(branch))) {
8692
core.warning(
8793
`Branch \`${branch}\` does not name any declared issue. ` +
8894
`Convention: claude/issue-<n>-<slug> (e.g. claude/issue-${[...mine][0]}-short-slug) ` +

0 commit comments

Comments
 (0)