Repository navigation
ci: classify a PR by its own diff, not by everything main gained since - #2105
Conversation
#2104) The `changes` job used a two-dot diff against `github.event.pull_request.base.sha`. That SHA is the base branch tip at the moment the event fired, not the PR's merge-base, so the diff also reported every file main gained since the branch diverged -- with the sign inverted, since the branch "lacks" them. A pure-markdown PR is then classified as code and takes the full matrix. Measured on #2103, which changes one .md file: two-dot reported 9 files, three-dot reported 1, and `gh pr view --json files` (ground truth) reports 1. The 8 extra are exactly #2091's file list -- #2091 being the commit that `base.sha` pointed at. Intermittent, not constant: #2070 (three .md files) classified docs_only=true and skipped all nine lanes, because its branch happened to be level with main when the event fired. The trigger is exactly "main moved between the merge-base and the event", which is why the fast path looks like it works whenever you check it deliberately. Fail-closed direction -- more CI, never less -- so this is cost and latency, not correctness. It matters because runner capacity is the binding constraint. `push` stays two-dot: before..after is the push itself. Failure behaviour is unchanged: if the merge-base is unavailable the diff errors, `|| true` leaves `files` empty, and the existing `elif [ -n "$files" ]` leaves docs_only=false. `fetch-depth: 0` is already set on this job. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
40da93f to
8f011c9
Compare
|
Independent Opus review: no MAJOR issues, and it independently re-derived the safety argument rather than taking mine. One MINOR, fixed in The MINOR was a wrong citation, produced by the exact command our own skill warns aboutI wrote that the 8 extra files were #2080's. They are #2091's. Verified before accepting the correction:
I got #2080 from Corrected in all four places it had propagated to: the Also took the NITThe comment now cites #2103 for the observation and #2104 for the tracking issue, rather than blurring them. What the review confirmed independentlyWorth recording, because these were the load-bearing safety claims and they were re-derived rather than accepted:
No behaviour change from the fix, so the 9/9 battery in the description still stands; the amend touched a comment and the commit message only. Re-verified the YAML parses and the job set is still 9. Marking ready. |
|
Live evidence from this PR's own run, which is better than my local battery because it is the real runner, on the real base, with the changed workflow actually in effect.
One file, correct verdict, and the embed-count guard is healthy — no The counterfactual, at the base the job actually ran onPulled the base out of the job's own The 12 extras span Worth being exact about what this does and does not show: this PR's verdict is unchanged either way, because That is also the honest shape of the whole bug: it mostly does not change the answer, because most PRs contain code and the answer was already |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2105 +/- ##
==========================================
+ Coverage 80.32% 81.17% +0.84%
==========================================
Files 426 429 +3
Lines 204769 214755 +9986
Branches 204769 214755 +9986
==========================================
+ Hits 164483 174322 +9839
+ Misses 34665 34664 -1
- Partials 5621 5769 +148
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Closes #2104.
The defect
The
changesjob classified a PR with a two-dot diff againstgithub.event.pull_request.base.sha. That SHA is the base branch tip at the moment the event fired, not the PR's merge-base — so the diff also reports every file main gained since the branch diverged, with the sign inverted, since the branch "lacks" them. A pure-markdown PR is then classified as code and takes the full matrix.Found it by checking my own PR #2103, which changes exactly one
.mdfile, and seeing all nine lanes queue.git diff --name-only $BASE $HEAD(two-dot, before)git diff --name-only $BASE...$HEAD(three-dot, after)gh pr view 2103 --json files— ground truthThe eight extras are exactly #2091's file list — and
base.shais #2091's merge commit. (I first wrote #2080 here; see the review response in the comments for how that wrong citation was produced and verified away.)It is intermittent, and that is the interesting part
My first inference was that the fast path essentially never fires. That was wrong, and I checked before writing it down. #2070 (three
.mdfiles) classifieddocs_only=trueand skipped all nine lanes — its branch happened to be level with main when the event fired, so two-dot and three-dot agreed.The trigger is precisely main gained a commit between the PR's merge-base and the event. Which means the fast path works whenever you go looking at it on a quiet tree, and silently doesn't on a busy one. Same shape as the stale-base problem: the answer depends on where main was standing, not on the PR.
Why it is worth fixing even though it is safe
The direction is fail-closed — more CI, never less — so this is cost and latency, not correctness. It matters because runner capacity is the binding constraint: queue depth has been in the 50s today, and a docs PR taking the full nine-lane matrix displaces work that actually needs it.
Safety
pushstays two-dot —before..afteris the push itself, and three-dot would be wrong there.|| trueleavesfilesempty, and the existingelif [ -n "$files" ]leavesdocs_only=false→ full CI.fetch-depth: 0is already set on this job..mdcompiled in byinclude_str!is source, not docs) is untouched and is arm C below.Battery — 9/9
The step's script was extracted from the YAML and run under the runner's own shell (
bash --noprofile --norc -eo pipefail), against real SHAs:false← the bugtruefalse.mdedit to aninclude_str!target (#2077)false.mdedit, not an embed target — the controltrue.md+.rsfalsefalsefalseschedule)falsefalsefalse(unchanged)C and C2 are the pair that carries the claim. Without C2, arm C passes on a classifier that answers "code" to literally everything. My first C2 fixture used
docs/execution/CUDA_COVERAGE.mdand the arm failed — that file is itself one of the two.mdembed targets, reached by a relative path. The fixture was wrong, not the code. Worth stating because a control that fails looks exactly like a defect, and I nearly filed it as one.Arm A's
oldcolumn is the positive control for the harness: it proves the battery can produce a FAIL rather than agreeing with everything.Expected CI on this PR
This PR changes
ci.yml, so it is code — it must run the full matrix, anddocs_only=falsehere is the correct answer, not a symptom. #2103 is the one that should flip to docs-only once this lands.Requesting independent Opus review.