Repository navigation
fix(ci): count a lane cancelled after its steps finished as passed - #12602
kyle-sexton wants to merge 7 commits into
Conversation
GitHub can hold a job open after Complete job until its timeout-minutes ends, then record it cancelled; ci-status then fails and the merge queue evicts a healthy PR. ci-status now re-reads such a lane's jobs and counts it as passed only when every step finished and the job ended at least 10 s after Complete job. Each cancelled lane writes a notice to count. Closes #12520 Closes #12506 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VQb9Q1Asjw331T5WUQHJyF
… gap Jobs cut off between steps showed gaps up to 33 s on pull_request runs, so a 10 s gap does not show the work ran. Each lane now ends in a Lane finished step, which runs only when no earlier step failed or was cancelled, and ci-status requires it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VQb9Q1Asjw331T5WUQHJyF
kyle-sexton
left a comment
There was a problem hiding this comment.
Second-reader review for Lane 1, at head 6b9cb15. Verdict: one small fix before merge, otherwise sound. I found no way for a lane with finished-but-unrun work to pass except the skipped branch below.
Fix before merge
A job that ran no steps can make a cancelled lane pass. In the job test, .conclusion == "skipped" is accepted with no look at its steps. Run against {"test-python":{"result":"cancelled"}} with the single job {"name":"test-python (0)","conclusion":"skipped","steps":[]}, the rule prints test-python, so the lane counts as passed with no work run.
I found no real path that produces this: in the runs I read, a skipped row comes with a skipped lane result, so it never reaches this branch. That is why it costs nothing to remove. Only test-bash and test-python have matrix legs, both fail-fast: false, so nothing needs a skipped leg here. Suggest all(.conclusion == "success" or (.conclusion == "cancelled" and finished)), plus a test row for it.
The questions you asked
- Can
Lane finishedsucceed after a cancel? No.StepsRunner.csinactions/runner(read atmain) sets the job status to cancelled before it evaluates the next step's condition, so the defaultsuccess()is false and the step is recorded skipped. A runner shutdown marks the job failed, which is also closed. If a cancel lands after that step's condition passed, all the earlier work has already finished. continue-on-errorsteps. Not a new hole.ApplyContinueOnErrorreturns early unless the result isFailed, so it does not turn a cancelled step into a success, andLane finishedis skipped either way. Inlint-repo,lint-shell,check-pluginsandcheck-skills, a gate that failed undercontinue-on-errorstill reaches the lane's aggregator, which runs beforeLane finishedand fails the job, so the lane is notcancelled.select-testshas two such steps and no aggregator; the rule treats them exactly as a job that finished normally does. I did not check whether the REST step row shows such a step asfailureorsuccess:failureonly loses a recovery, andsuccessmatches what a finished job already gets.- Matrix legs. Sound apart from the fix above. A leg with no steps fails
finished, and any failed sibling fails theall. Job names come from the job keys: no needed lane setsname:, so the exact match and the<lane> (prefix cover every leg. - Re-run attempts. Sound. On run 41561143343,
attempts/2/jobskepttest-bash (0)from attempt 1 with the same timestamps and step count under a new job id, while onlyci-statusre-ran. Aci-status-only re-run therefore reads the same evidence on the same SHA. That is one run; a job row without steps fails closed anyway.
Also checked
- All 12 lanes end in
Lane finishedwith noif:, parsed with a YAML parser, so nothing runs after it. - The job has
actions: write, so the jobs read works. - A recovered
select-testskeeps its blocked lanes red unless its output row is exactlyfalse, which is a real answer onceLane finishedhas run. LANESand the lane names reachjqas data, so nothing is injectable.
Small, not blocking
- The awk guard in
ci-ordered-skips.test.shonly sees- name:lines, so a later unnamed- run:or- uses:step, or one that opens with- id:, would pass it. None exists today. Such a step could be cut and still count as passed. - The new comment's "a cancelled lane here reached its limit" is stronger than the rule needs. The rule holds for any cause of cancel, so I would drop that inference.
Not shown
As your body says, no real run has the new step yet. If a real cut job does not record Lane finished as expected, the lane stays cancelled, so the risk is lost recovery rather than a false pass. The cancelled-after-success notices will show which.
I posted this as a comment because every lane shares one GitHub login, which cannot approve its own PR. The do-not-merge label is yours to lift once the fix is in; no re-review is needed for that one line and its test.
🤖 Generated with Claude Code
Second-reader finding: the job test accepted a skipped job without looking at its steps, so a job that ran nothing could pass the lane. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VQb9Q1Asjw331T5WUQHJyF
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
claude-security-review reviewed Security review: PR #12602 (
|
|
claude-review reviewed Code review: PR #12602, count a lane cancelled after its steps finished as passedI read the full diff for both changed files: The change is correct
Closing issue #12520 acceptance criteria
Notes
|
|
Closed in favor of #12597 (Lane 5), agreed with Lane 5. It gives each lane job its work budget plus 6 minutes, which changes no gate logic and also covers select-tests. Lane 5 owns #12520 and #12506 and reads the cancelled-after-success notices. If evictions persist, branch fix/12520-cancelled-after-success holds this version as a fallback. |
Closes #12520
Refs: #12506
Summary
A job that finished every step but that GitHub recorded
cancelledat itstimeout-minutesno longer failsci-statusor evicts a healthy PR from the merge queue. This is the one required check, so the rule is deliberately narrow: a cancelled lane counts as passed only on proof that all its work ran.Fix
ci-statusneeds now ends in aLane finishedstep. Its defaultif: success()means it runs only when no earlier step failed or was cancelled.ci-status'sCheck each skipped lane against scopestep reads this run attempt's jobs when a lane iscancelled. The lane counts as passed only when each of its jobs (every matrix leg) passed, or was cancelled withLane finishedsucceeded, no step worse thanskipped, andComplete joblast.cancelled-after-successnotice withcounted-as-passed=true|false, the count to compare against the baseline on fix(ci): jobs recorded cancelled after all steps succeeded fail ci-status and evict PRs from the merge queue #12520.select-testscan count as passed under the rule, but the lanes it blocked stayskipped, so the run stays red. Their work never ran.An earlier version used a 10 s gap after
Complete job. It is dropped: jobs cut off between steps showed gaps up to 33 s in the measured sample (correction).Verification
merge_grouprun's concurrency group is unique per run. The job logs end normally, so they do not show why GitHub left the job open.scripts/ci-ordered-skips.test.sh: 41 of 41 pass, running the step body with a stubbed jobs read. Rows cover a limit between two steps, a step cancelled mid-run, a failed step, a failed matrix sibling, a failed read, a name-prefix near match, a blockedselect-tests, no API call without a cancelled lane, and every needed lane ending inLane finished.check-affectedpassed.select-testshangs. Thecancelled-after-successnotices will show the real number.Related
lint-repoinstance of the same state, triaged a duplicate of fix(ci): jobs recorded cancelled after all steps succeeded fail ci-status and evict PRs from the merge queue #12520. Lane 1 closes it as a duplicate once this merges.🤖 Generated with Claude Code
https://claude.ai/code/session_01VQb9Q1Asjw331T5WUQHJyF