Uh oh!
There was an error while loading. Please reload this page.
ci(lint): run scripts/pm/ci-failure.mjs --self-test in Lint & Repo Gates - #10195
Merged
Conversation
`scripts/pm/ci-failure.mjs` shipped a `--self-test` that no job ran, unlike the four sibling PM tools (dispatch-gates, the part-of guard, the single-claim guard, the half-state sweeper) which all run theirs as unconditional steps in this same job. It was the odd one out, so it could rot until the next agent reached for it mid-round and found it broken. The rot this prevents is the exact defect the tool's own card was about: the file was found near-complete but never run live, because node 22's `fetch` ignores HTTPS_PROXY, so every read answered 401 and the tool's own transport probe reported PREREQUISITE NOT MET (exit 3) — and looked right doing it. A retrieval tool that has stopped working presents as a tool correctly declining to work. One step in the existing `lint:` job, whose `name:` (`Lint & Repo Gates`) is the required-status-check context and is unchanged — a required context is matched by check-run name, so a step adds no context. No new job, no new check name. Unconditional and un-`if:`-ed, like every self-test beside it. Measured, not assumed: under `strace -f` with full egress available the self-test makes zero socket() and zero connect() calls (the live walk, traced identically, makes 3 and 3), and it exits 0 inside an empty network namespace where the live walk exits 3. Runtime ~0.07-0.17 s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
os-zhuang
marked this pull request as ready for review
August 20, 2026 13:53
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9898
scripts/pm/ci-failure.mjsshipped a--self-testthat no job ran. Its foursibling PM tools all run theirs as unconditional steps in the existing
Lint & Repo Gatesjob; this one was the odd one out, so it could rot until anagent reached for it mid-round and found it broken.
This adds one step to the existing
lint:job. No new job, no new checkname, no new required context.
Why this tool specifically
The rot it prevents is the exact defect the tool's own card was about. The file
was found near-complete but never run live: node 22's
fetchignoresHTTPS_PROXY, so behind an agent container's proxy every read answered 401 andthe tool's own transport probe reported
PREREQUISITE NOT MET(exit 3) — andlooked right doing it. A retrieval tool that has stopped working presents as a
tool correctly declining to work.
The self-test is also materially larger than when the card was filed: #9966 /
PR #10157 (merged today) corrected the transport probe and added nine cases,
including the fourth measured container class. This card was deliberately
ordered after that fix — wiring a false green into a required job would have
pinned it as CI-enforced truth.
Falsification 1 — the step is in the right job (job key + line)
The card's own warning: a gate added to
typecheck:shows green ticks whileblocking nothing, reproducing this card's failure mode as its fix. Read from
.github/workflows/lint.yml, not from the card:name:(= check-run / required context)lint:Lint & Repo Gates(line 39)typecheck:TypeScript Type CheckThose are the only two jobs in the file. The trap is live:
typecheck:reallydoes carry self-test steps of its own, so "this file has self-test steps" would
not have identified the right job.
Parsed rather than grepped — all five land in
lint/Lint & Repo Gates, nonecarries an
if::if:PM dispatch-gates self-testlintPart-of closing-keyword guard self-testlintSingle-claim path guard self-testlintPM half-state sweeper self-testlintPM ci-failure self-test(new)lintThe new step is index 23 of 74 in
jobs.lint.steps, keys['name', 'run'].No required-context registry edit is needed, and none is possible by accident:
scripts/check-required-contexts.mjsassertion 10 explicitly forbids a registryentry from embedding a step count, precisely so adding a step cannot desync it.
Falsification 2 — it needs no network (measured, not inferred)
A self-test that reached GitHub would put this required context at the mercy of
API availability. "It worked here" is not that claim, so it was measured three
ways, each with its instrument falsified against this same file's live path:
--self-teststrace -f, network availablesocket(), 0connect()socket(AF_INET), 3connect()unshare -rn)GET /rate_limit did not complete: fetch failedThe
stracereading is the load-bearing one: zero socket syscalls with fullegress available, following forks, so nothing is hidden in a subprocess.
Note on the tripwire, recorded because it nearly produced a false reading: its
fetcharm fires correctly, but itschild_processarm is blind to a namedESM import binding, which is exactly how this file imports
spawnSync. Caughtby falsifying the instrument rather than trusting its "clean".
straceand thenamespace have no such blind spot.
Structurally this is not accidental: the
--self-testbranch is selected beforethe transport probe, it never re-execs, and its two readers are injected.
Falsification 3 — the step runs, rather than no-opping
"I added the step and CI is green" does not distinguish a step that ran from one
that no-opped, so both no-op shapes were driven:
isNewerin the shipped file(
return at > bt→at < bt; confirmed on disk by grep count 1→0 / 0→1, notby an editor exit code) makes the step's exact command exit 1 with two
named failures. Restored to byte-identity with
HEADand re-run green.ERR_MODULE_NOT_FOUND) rather than exiting0 the way a zero-match
pnpm --filterwould.Runtime
~65–170 ms, median ~0.10 s (8 runs), against a job that already runs ~70 gate
steps including a ~15 s one. It reads files and runs pure predicates.
Gates
node scripts/pm/dispatch-gates.mjswith no path args, derived from the realdiff at
b3955be:check:node-version,check:required-contexts,check:shard-attestation,check:workflow-status-functions,check:type-check-coverage— all green.Extra, because editing
lint.ymlmoves gates no path derivation names (found bygrepping for readers of the file):
check:changeset-gate-self-tests(itswiring half slices the
lint:job out by anchors and asserts the slice isnon-empty — green, so the insertion did not stale those anchors),
check:filter-alias-parity,check-merge-queue-triage-outcome --self-test,check-cross-repo-closer-outcome --self-test,check:nul-bytes— all green.Declared narrowing:
check:type-check-debt(--re-measure) was not run. Itre-runs
tscacross the debt ledger to re-measure error counts; this diffcontains no TypeScript. CI runs it regardless.
skip-changeset: this diff is one workflow file and publishes nothing.Generated by Claude Code
Generated by Claude Code