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
Commit a2a9741
Browse filesBrowse the repository at this point in the historyBrowse files
fix(ci): collect every self-test verdict in a step, instead of masking after the first failure
GitHub runs a `run:` block as `bash -e <file>`, so the first non-zero exit
aborts the step and every command after it is never reached -- neither green
nor red, and nothing in the log tells those two apart.
Measured, not hypothetical: lint.yml's `Shallow-history guard self-tests` ran
three independent self-tests as a bare sequence with
`git-history.mjs --self-test` first, and that one was red on `main` for about
ten hours on 2026-08-21 (#10807). For the whole of that window
`check-engine-split-ratio.mjs --self-test` and
`collect-release-notes.sh --self-test` did not execute in CI once, on the step
that gates every PR. Both were green, so the mask cost nothing that day; the
expensive shape is the one it leaves behind, where a second regression lands
unnoticed while the first is red and then reads as though the fix broke it.
Both steps that had this shape now run every self-test unconditionally through
a `run_self_test` collector, print a verdict per self-test, and exit non-zero
at the end naming every one that failed.
Not a split into one step per self-test: a plain split does not fix this at
all, because Actions skips a job's remaining steps once a step fails, so the
mask survives the split verbatim. Restoring the property would take an `if:`
on each gate step, and a condition is a way for a PR to arrange that a gate
does not run on it. Both gates that read step structure were checked and would
have tolerated a split -- `check-shard-attestation` scans ci.yml only, and
`check-required-contexts` pins job-level properties plus the single
`check:required-contexts` step -- so the split was rejected on the merits, not
because a gate refused it.
`scripts/check-step-collectors.mjs` closes the class in both halves. Statically
it requires any `run:` block invoking `--self-test` on two or more DISTINCT
scripts to route them through a collector, and deliberately does not flag the
`<gate> --self-test` + `<gate>` precondition shape that dominates this job.
Dynamically -- the load-bearing half, since nothing static can tell a collector
that propagates the exit code from one that swallows it -- its `--self-test`
extracts each live block out of lint.yml and runs it as `bash -e <file>`
against stubs with controlled exit codes, reading "did this command run" from
the stub's own side effect rather than from the block's output. The same
command list is also driven through the pre-fix bare sequence, which must mask.
Census behind the narrow rule: 343 `run:` steps across 26 workflows, of which
exactly two carried two or more distinct self-tests, both in lint.yml's `lint`
job and both fixed here. Every other multi-command block is a precondition or
a dependency sequence, where abort-on-first-failure is the correct semantics.
Part of #10814
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
0 commit comments