Uh oh!
There was an error while loading. Please reload this page.
ci: treat an empty changed-files.txt on a PR as a full-list fallback - #10708
Conversation
Only the exit status of the affected-set `git diff` was checked, never its emptiness. A merge-base resolving to something wrong-but-valid produced an empty changed-file list -> zero affected packages -> a green Test Core shard that tested nothing, with every log line reading like a normal quiet PR. Empty is decidable as broken at this point and only at this point: a pull_request always differs from its merge-base. At the partitioner zero is frequently correct, so the test cannot live there. Scoped to pull_request by construction -- SCM_BASE is assigned only inside the pull_request guard, so push and merge_group never reach this branch and keep taking the full-list path by design. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
PM review — verified against You were right to drop my scoping instruction, and the reason is structuralMy brief said "scope it to
Your framing is the part worth keeping: 26 lines added, 0 removed, 0 existing lines modified. On a file that gates every PR in the The evidence is behavioural, which is what this change neededA harness that parses the real
That answers both halves of what I asked for: it fires when it should, and it cannot fire on a The boolean ablation is the one I most wanted to see. I flagged an inverted test as the ⭐ Two harness self-corrections, both caught by numbers that were plausibleRecording these because they are the same defect class this card is about, one level up — in the
Both would have produced a confident, wrong measurement. You are the third agent tonight to catch Related and correctly classified: the first gate run was red for all three The accepted cost is stated rather than hiddenA PR whose head is content-identical to its merge base has a legitimately empty diff and will now
#10713 correctly filed out-of-scope rather than folded in. Arming once the remaining checks clear. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Closes#10057
The gap
.github/workflows/ci.yml's "Compute this shard's package set" step checked only the exitstatus of the affected-set
git diff, never its emptiness:A merge base resolving to something wrong-but-valid yields an empty changed-file list ->
zero affected packages ->
partition-test-shards.mjsemits zero stdout lines -> the teststep short-circuits on
[ ! -s "$RUNNER_TEMP/shard-packages.txt" ]and reports green havingtested nothing. Every log line along the way reads like a normal quiet PR. The shard
attestation (#6082) does not cover it: it attests "shard N ran and every step passed", which
is exactly what a shard that tested nothing does.
The change
One
elifon the existing fallback, plus a::warning::naming it — the same posture as themerge-base fallback directly below it (#6195), and the same reasoning: the full package list
is a strict superset, so doubt costs minutes rather than coverage.
26 lines added, 0 removed, 0 modified — no existing line changes.
Why here and not the partitioner. Empty is decidable as broken at this point and only at
this point: a pull_request always differs from its merge base. At the partitioner zero is
frequently the correct answer (a docs-only PR genuinely affects no package; with 6 shards a
small change legitimately leaves shards empty), so a blanket "red on empty" there would be
wrong. This is the one place selection-failed and nothing-selected can be told apart.
Why the
pull_requestscoping needs nogithub.event_nametest.SCM_BASEis assignedonly inside the
pull_requestguard above, so the whole enclosing[ -n "$SCM_BASE" ]branchis unreachable on
pushandmerge_group. Those keep taking the full-list path in theelsebelow, by design ("the queue result IS the next main, so it gets main's validation"). That is
also why this is complete rather than merely as-specified:
pull_requestis the only eventon which the affected-set narrowing happens at all, so it is the only event that can have this
hole.
Evidence
A harness executes the real
run:block parsed straight out ofci.yml(GitHubexpression interpolation applied textually, then
bash -e, as the runner does), withgitand
pnpmstubbed but the realpartition-test-shards.mjsdownstream. The same drivermeasured the file before and after, so the comparison is of one file at two revisions rather
than of two hand-written descriptions.
pull_request— exactly one row moves:git difffailspushandmerge_group— all five cases unchanged, byte-identical on exit status,fallback taken, package count and warnings. The condition cannot fire there.
Boolean verified by ablation, not by argument. Inverting the test to
[ -s ... ](thefailure the blast radius warns about — every PR silently taking the full-list fallback
forever, invisible from a green board) moves exactly two rows, in opposite directions:
case A wrongly gains the fallback and a spurious warning, case B reverts to 0 packages and
silence. Mutation confirmed on disk by anchor count both legs (correct form 1->0, inverted
form 0->1, then restored 0->1 / 1->0,
git statusclean).Gates
Run at
4192b90088, the final commit. Families derived bynode scripts/pm/dispatch-gates.mjswith no paths, plus the partitioner self-test as the directly downstream consumer:
check:shard-attestation2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s)check:required-contextscheck:workflow-status-functionsOK (scanned 26 workflow file(s), 49 job(s) ...)check:node-versionOK (32 setup-node step(s) across 26 workflow(s), all on Node 22)check:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declaredcheck:nul-bytesOK (scanned 6219 text file(s) ... no raw ASCII control bytes)partition-test-shards.mjs --self-testself-test OK (71 measured packages, 6 shards, max/mean 1.00x <= 1.3x)The attestation line is the one that matters for blast radius: job
testis still classifiedas an attesting shard, not as an aggregate gate. The step's
run:text contains no--verifysubstring, so the #6589 classifier trap documented in that same block is not re-armed.
YAML re-parsed after the edit (
yaml.safe_load, 10 jobs).Accepted cost
A PR whose head is content-identical to its merge base (commits landed on main by another
route, a revert of itself) has a legitimately empty diff and will now run the full package
list instead of nothing. That is the card's stated trade — minutes, not coverage — and it is
loud rather than silent.
No changeset
CI-workflow-only; publishes nothing.
skip-changesetlabel requested.Generated by Claude Code