You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] scripts/pm/git-history.mjs's --self-test runs nowhere in CI — the sixth instance of a class fixed five times one at a time #10196
Found while wiring scripts/pm/ci-failure.mjs's --self-test into Lint & Repo Gates for #9898 (PR #10195). Not fixed there — that card's declared file surface is .github/workflows/lint.yml and this is a different tool. Filed unassigned.
The observation
scripts/pm/git-history.mjs ships a --self-test (dispatch at :306, reporter at :503, documented in its own usage block at :13) and nothing runs it: no workflow names it, and root package.json carries no alias for it.
Derived at origin/main by resolving pnpm check:* aliases rather than grepping workflows for filenames — the naive filename scan is wrong here, because most PM tools reach CI through an alias and their filename never appears in any workflow:
(dispatch-gates.mjs is covered indirectly: pnpm check:pm-dispatch-gates runs the dedicated check-dispatch-gates.mjs gate, whose header argues why it is a separate file.)
Why this is worth a card rather than a shrug
This exact class has now been closed five separate times, one instance at a time: #8110, #8170, #8162, #8528, #9348 — and #9898 is the sixth. Every one was found by a human or an agent noticing, never by a gate. The rot is quiet by construction: a self-test nobody runs stays green in the only sense that matters (nobody looks), and the tool it guards is typically reached for mid-round, under time pressure, by someone who needs it to work right then.
git-history.mjs also sits in the blast radius of #9902 (seat tools asking windowed history questions with no shallow guard), which makes an unrun self-test on it worth more than average.
Suggested shape — close the class, not the instance
Rather than a seventh manual wiring, a gate that asserts every scripts/pm/** tool shipping a --self-test is reachable from some workflow (directly or through a package.json alias), with an explicit, reasoned opt-out list for anything deliberately excluded. The alias-resolution above is the whole detector; it is ~20 lines and it already exists in prose in several step comments in lint.yml.
That converts "somebody notices the seventh instance" into "the seventh instance cannot merge unwired", which is the same trade every step in the Lint & Repo Gates self-test family already accepts.
⚠️ Worth measuring before building: whether the runtime of git-history.mjs --self-test is CI-affordable, and whether it needs network. Neither was measured here — only its wiring was.
Corroborating measurement from the #10086 dispatch (which touched this file to convert its entry guard).
This card says scripts/pm/git-history.mjs --self-test runs nowhere in CI. It is worth recording that the self-test is currently red, and has been for at least as long as origin/main @ 923c424:
$ node scripts/pm/git-history.mjs --self-test
✗ and the answer is the real one (21 commits: the daily fixture commits i=19..39)
✗ and the answer now MATCHES the complete clone
✗ a still-shallow clone whose floor predates the window answers WITHOUT fetching
(a bare is-shallow guard would have refused this correct answer)
git-history --self-test: 3 FAILED.
Measured on unmodified origin/main in a clean worktree, and byte-identically on the #10086 branch — so it is not caused by that change. The remote was reachable from the container (git ls-remote succeeded), so this is not simply "no network"; the three failing cases are the shallow-clone deepening fixture, and the failing assertion names a daily commit window (i=19..39), which suggests time-dependence rather than a hard break.
That sharpens this card's argument rather than adding a new one: the gap named here is not hypothetical. A self-test that no CI job runs has had three red cases sitting in it, and nothing reported them — which is the same silence-reads-as-pass shape, one level up. Whoever wires it should expect it to go red on the first run and budget for fixing the fixture, not just for the wiring.
Filed as a comment rather than a new issue because the subject is this file's self-test, which is what this card is about.
Input for the detector this card proposes, derived while doing the root scripts/** half in PR #10649. Recording it here because that PR closes its own card, and the two things this card will need — the predicate and the reasoned opt-out list — would go with it.
1. The predicate matters more than the alias resolution
--self-testappearing in a file is not the same as a file shipping one. On today's main, matching the mention gives 92 root scripts/*.mjs; requiring an actual process.argv branch for the flag gives 83. The nine-file gap is real files that only discuss the flag in prose — adr-anchors.mjs, eslint-fatal-guard.mjs, regen-artifacts.mjs among them — and a gate built on the mention will demand wiring for tools that have nothing to run.
The consequence is not academic: the sub-card's table was built on the looser predicate and reported 87 shipping / 6 unreached where the stricter one gives 82 / 9 at that same commit. Both columns moved, and two genuinely unreached scripts were missing from the list.
Suggested predicate: strip block and line comments, then require argv within ~60 chars of a '--self-test' literal.
2. The reasoned opt-out list, for root scripts/**
After PR #10649 wires four (ts-parse.mjs, js-comment-mask.mjs, invoked-as.mjs, check-optional-error-sink-contract.mjs), four remain unreached and each is deliberate:
script
why it should be an opt-out, not a wiring
setup-git-hooks.mjs
Its --self-test asserts a property of the current clone (core.hooksPath + two merge-driver settings), not of the committed tree. Its header states the registrar warns and exits 0 on every failure path so that "CI images that install with --ignore-scripts" are not broken. Wiring it reds a required context on an environment fact no PR changed.
checklist-select.mjs
Reachable only via check:platform-checklist, which lint.yml already documents as excluded by maintainer decision — "it runs on a periodic manual cadence … a QA ledger, not a code gate". A detector that demands it re-opens a lane a ruling closed.
measure-test-shard-timings.mjs
A by-hand generator over .turbo/runs/ artifacts; its product is committed and the consumer side is already pinned in CI (partition-test-shards.mjs --self-test at lint.yml:1367, and loadTimings() refuses a missing or malformed dataset rather than falling back).
ablation-dist-preflight.mjs
The closest call. Cost is not the argument (~0.07 s). Left because no CI gate consumes it and no gate's design argument delegates to it — its consumers are agents, not workflows. Worth revisiting if that changes.
The first two are the interesting ones for the gate's shape: an opt-out list that cannot express "this is an environment assertion" or "a ruling excluded this lane" will get argued with rather than maintained.
3. Affordability, which this card flags as unmeasured
Measured for the four now wired: ~14 s total per PR, of which ts-parse.mjs --self-test is ~10 s because it spawns ~20 real node children (the spawns are the pin — a caller's try/catch must not be able to swallow the refusal). The sub-card budgeted ~4 s for it, so a detector that lands more of these should assume roughly 2.5x its own estimate. None of the four needs network.
Closing as already fixed — premise re-derived at origin/main = 01c50322a1 (fetched 2026-08-21T10:51:38Z), before dispatch.
scripts/pm/git-history.mjs --self-testdoes run in CI now. It landed in #10506 (commit 7f8b36086f, 2026-08-21T02:10:05Z) as the first line of the Shallow-history guard self-tests step:
That PR was not aimed at this card, but its comment at lint.yml:518 states this card's observation in its own words:
git-history.mjs is listed first and deliberately: it is the shared predicate all three call, it shipped for #9878 with a self-test that until now NOTHING in this repo ran, and an unrun self-test is a phantom check
It also independently reached this card's conclusion about the route: it invokes the tool as node rather than adding a pnpm check:* alias, because root package.json is declared territory of the @changesets/cli v3 migration lane (#9465) while that lane runs. So the fix took the only shape available, and a dispatch against this card today would have collided with #9465's fence before it collided with anything else.
Two things worth recording rather than losing with the card:
check-engine-split-ratio.mjs and collect-release-notes.sh were swept in alongside it, neither of which appeared in this card's table — so the unwired population at filing time was larger than the one instance measured here. That is the ordinary shape of a hand-kept census, and the reason for point 1.
No code change was needed. Dropping pm:queue; not dispatched, so no pm:dispatched was ever applied.
Activity
os-zhuang commentedon Aug 20, 2026
Corroborating measurement from the #10086 dispatch (which touched this file to convert its entry guard).
This card says
scripts/pm/git-history.mjs --self-testruns nowhere in CI. It is worth recording that the self-test is currently red, and has been for at least as long asorigin/main@ 923c424:Measured on unmodified
origin/mainin a clean worktree, and byte-identically on the #10086 branch — so it is not caused by that change. The remote was reachable from the container (git ls-remotesucceeded), so this is not simply "no network"; the three failing cases are the shallow-clone deepening fixture, and the failing assertion names a daily commit window (i=19..39), which suggests time-dependence rather than a hard break.That sharpens this card's argument rather than adding a new one: the gap named here is not hypothetical. A self-test that no CI job runs has had three red cases sitting in it, and nothing reported them — which is the same silence-reads-as-pass shape, one level up. Whoever wires it should expect it to go red on the first run and budget for fixing the fixture, not just for the wiring.
Filed as a comment rather than a new issue because the subject is this file's self-test, which is what this card is about.
Generated by Claude Code
scripts/**: 6 unwired, including ts-parse.mjs — whose unrun pin is what check:parse-guard's design argument rests on #10608claude commentedon Aug 21, 2026
Input for the detector this card proposes, derived while doing the root
scripts/**half in PR #10649. Recording it here because that PR closes its own card, and the two things this card will need — the predicate and the reasoned opt-out list — would go with it.1. The predicate matters more than the alias resolution
--self-testappearing in a file is not the same as a file shipping one. On today'smain, matching the mention gives 92 rootscripts/*.mjs; requiring an actualprocess.argvbranch for the flag gives 83. The nine-file gap is real files that only discuss the flag in prose —adr-anchors.mjs,eslint-fatal-guard.mjs,regen-artifacts.mjsamong them — and a gate built on the mention will demand wiring for tools that have nothing to run.The consequence is not academic: the sub-card's table was built on the looser predicate and reported 87 shipping / 6 unreached where the stricter one gives 82 / 9 at that same commit. Both columns moved, and two genuinely unreached scripts were missing from the list.
Suggested predicate: strip block and line comments, then require
argvwithin ~60 chars of a'--self-test'literal.2. The reasoned opt-out list, for root
scripts/**After PR #10649 wires four (
ts-parse.mjs,js-comment-mask.mjs,invoked-as.mjs,check-optional-error-sink-contract.mjs), four remain unreached and each is deliberate:setup-git-hooks.mjs--self-testasserts a property of the current clone (core.hooksPath+ two merge-driver settings), not of the committed tree. Its header states the registrar warns and exits 0 on every failure path so that "CI images that install with--ignore-scripts" are not broken. Wiring it reds a required context on an environment fact no PR changed.checklist-select.mjscheck:platform-checklist, whichlint.ymlalready documents as excluded by maintainer decision — "it runs on a periodic manual cadence … a QA ledger, not a code gate". A detector that demands it re-opens a lane a ruling closed.measure-test-shard-timings.mjs.turbo/runs/artifacts; its product is committed and the consumer side is already pinned in CI (partition-test-shards.mjs --self-testatlint.yml:1367, andloadTimings()refuses a missing or malformed dataset rather than falling back).ablation-dist-preflight.mjsThe first two are the interesting ones for the gate's shape: an opt-out list that cannot express "this is an environment assertion" or "a ruling excluded this lane" will get argued with rather than maintained.
3. Affordability, which this card flags as unmeasured
Measured for the four now wired: ~14 s total per PR, of which
ts-parse.mjs --self-testis ~10 s because it spawns ~20 real node children (the spawns are the pin — a caller'stry/catchmust not be able to swallow the refusal). The sub-card budgeted ~4 s for it, so a detector that lands more of these should assume roughly 2.5x its own estimate. None of the four needs network.Generated by Claude Code
Generated by Claude Code
claude commentedon Aug 21, 2026
Closing as already fixed — premise re-derived at
origin/main=01c50322a1(fetched 2026-08-21T10:51:38Z), before dispatch.scripts/pm/git-history.mjs --self-testdoes run in CI now. It landed in #10506 (commit7f8b36086f, 2026-08-21T02:10:05Z) as the first line of theShallow-history guard self-testsstep:That PR was not aimed at this card, but its comment at
lint.yml:518states this card's observation in its own words:It also independently reached this card's conclusion about the route: it invokes the tool as
noderather than adding apnpm check:*alias, because rootpackage.jsonis declared territory of the @changesets/cli v3 migration lane (#9465) while that lane runs. So the fix took the only shape available, and a dispatch against this card today would have collided with #9465's fence before it collided with anything else.Two things worth recording rather than losing with the card:
check:skill-frame-freshness --self-testruns nowhere in CI, so a broken fixture is invisible until a human runs the gate #8110,check:dev-prereqs --self-testruns nowhere in CI — the second and only other member of #8110's split-gate family #8170, [finding]dispatch-gates.mjshas 28 self-test cases and no CI job runs any of them #8162, [finding]check-half-states.mjshas a 79-case--self-testthat no CI job runs — and a blocking gate now imports its predicate #8528, [finding]scripts/sync-template-versions.mjshas no--self-testand runs nowhere in CI — the version-time rewriter is only ever executed on a release PR that CI does not trigger #9348) and that no gate has ever found one. That is still true — fix(pm-tooling): refuse windowed history questions a shallow clone cannot see all of #10506 found it the same way all six were found: a human or agent noticing while doing something adjacent. Nothing in this repo yet asserts "every--self-testa tool ships is run by something." If that gate is worth having, it is a separate card and it should be filed as one; this card's closure is not evidence the class is retired.check-engine-split-ratio.mjsandcollect-release-notes.shwere swept in alongside it, neither of which appeared in this card's table — so the unwired population at filing time was larger than the one instance measured here. That is the ordinary shape of a hand-kept census, and the reason for point 1.No code change was needed. Dropping
pm:queue; not dispatched, so nopm:dispatchedwas ever applied.Generated by Claude Code