Repository navigation
Commit 9a2e715
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
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
82 | | - | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
83 | 89 | | |
84 | 90 | | |
85 | | - | |
| 91 | + | |
86 | 92 | | |
87 | 93 | | |
88 | 94 | | |
| |||
0 commit comments