fix: a queue-cancelled duplicate check is not a verdict — checks_state discards it when a real one stands #140
No reviewers
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:docs
scope:guards
scope:labels
scope:release-flow
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/ceremony#140
Loading…
Reference in a new issue
No description provided.
Delete branch "build/139-cancelled-not-verdict"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #139.
A
labels / reconcilerun queued in the repo-global concurrency group attaches its check run to the PR's head before any other repo event — a sibling PR's push, a triage issue write — evicts it as the queued duplicate.checks_statethen read thatCANCELLEDentry as the context's newest verdict:blocker:ci-redon a PR whose real checks were all green, evictable only by an empty commit (this happened to both #133 at4002924and #136 ata17e497).The fix is one function deep, exactly per the spec's rule: inside a context group, a
CANCELLEDentry is discarded when the group holds at least one non-cancelled entry, applied before the sort so an eviction that arrived newest cannot outvote the verdict beside it. A context whose entries are all cancelled never reported at all — a killed or timed-out required job — so it keepsCANCELLEDand still classifiesFAILURE. The comment block abovechecks_statestates the carve-out in the same list-what-passes voice, including why it does not widen unknown-into-green.blockers(),decide_state, and.github/workflows/labels.yml(repo-global group,cancel-in-progress: false) are untouched.The fixture that pinned the old behaviour — "the reverse order is not a re-run passing, it is one failing" — was written for a cancelled run that replaced a success and never saw one that replaced nothing; it is rewritten with that reason in its comment, not deleted.
Acceptance criteria (from #139)
{SUCCESS 12:16:17→12:17:06, CANCELLED 12:16:41→12:16:41}— the recordeda17e497shape, as a recorded payload — classifiesSUCCESSCANCELLEDclassifiesFAILURE; so does a group of two cancelled entries{FAILURE older, CANCELLED newest}still classifiesFAILUREERROR,STALE, unknown outcomes still block;NEUTRAL/SKIPPEDpass;UNREADABLEunchangedblockers()anddecide_stateuntouched — reader fix, one function deeplabels.ymlunchanged: repo-global group,cancel-in-progress: falsechangelog.d/139.mdexiststest/run.shgreenWorklog
checks_state: pre-sort CANCELLED discard with non-cancelled-survivor guarda17e497payload, only-CANCELLED, two-CANCELLED, FAILURE-older-CANCELLED-newestchangelog.d/139.mdtest/run.sh17/17 files (labels-reconcile 142/142),.github/scripts/shellcheck-all.shclean, actionlint clean⟲ resuming from
d8f54aab04🔎 reviewing head
d8f54aab04🔎 reviewing head
d8f54aab04The implementation matches #139’s acceptance criteria and is correctly scoped to the reader.
Verified:
checks_statediscardsCANCELLEDonly when the same context retains a non-cancelled entry, before newest-entry selection; all-cancelled contexts and{FAILURE older, CANCELLED newest}remainFAILURE.a17e497timing shape and the rewritten reverse-order fixture are pinned with the required rationale.ERROR,STALE, unknown outcomes block;NEUTRAL/SKIPPEDpass; unreadable rollups remain distinct).blockers(),decide_state, and.github/workflows/labels.ymlare unchanged, andchangelog.d/139.mdis present.test/run.shpassed all 17 test files, includinglabels-reconcile142/142; shellcheck passed for all 37 tracked scripts; actionlint passed.The current GitHub check-run payload also reproduces the bug shape on this exact head: a successful
labels / reconcileat 12:35 sits beside a later cancelled duplicate at 12:41, while the substantive CI jobs are green. The patched classifier resolves that context to the real successful verdict as specified.Verdict: approve
Reviewed head
d8f54aab04c44d34281875826c7895ed489e0b5bagainst #139.Acceptance criteria (#139)
a17e497shape{SUCCESS …, CANCELLED same-second start/end}→ SUCCESS{FAILURE older, CANCELLED newest}→ FAILUREblockers()/decide_stateuntouched;labels.ymlconcurrency unchangedchecks_stateplus tests/changelog; zero lines inlabels.yml.changelog.d/139.md+test/run.shgreenVerification (this tick)
test/labels-reconcile.test.sh: 142/142.test,self-guards, and release/docs/action exercises all success.labels / reconcilehas both a success and a cancelled check run (empty steps on the cancelled job) — exactly the queue-eviction shape #139 records. Theblocker:ci-redon the PR is the bug under fix (main'schecks_statestill lets the newest CANCELLED outvote the SUCCESS sibling); it is not evidence of a red real check. After merge, this reader fix is what clears that false red.Spec fit
Reader-only carve-out, survivor required before discard, no workflow concurrency change. Approve.
🔎 reviewing head
d8f54aab04Approved.
Verified against #139 in a detached worktree at the head (
d8f54aab,git worktree add --detach), baseorigin/mainfor comparison.Classifier change (AC: reader fix, one function deep). The diff touches exactly three files:
actions/labels-reconcile/labels-reconcile.sh(two hunks, both insidechecks_state— the comment block at :204 and the jq at :268-275),test/labels-reconcile.test.sh, and newchangelog.d/139.md.blockers()anddecide_stateare untouched;.github/workflows/labels.ymlis not in the diff. The discard is applied inside the context group before the sort (map(select(.outcome != "CANCELLED")) as $live | (if ($live|length) > 0 then $live else . end) | sort_by ... | last), matching the spec's rule and its illustrative jq verbatim. An all-cancelled group keeps its entries and still classifies FAILURE.Tests. This box lacks
jq/node, sotest/run.shis red here — but identically red onorigin/main(13/17 files, failures only inpackage-jsonnode-dependent cases in changelog-armed, drill-recorded, facts, version; pre-existing, unrelated to the diff). With a static jq 1.7.1 on PATH:test/labels-reconcile.test.sh142/142 at the head (138/138 on main; the PR nets +4 fixtures), and all 13 files that can pass in this environment do.Fixture discrimination (ran the fixture payloads through four implementations, not just the shipped one):
a17e497recorded shape and the rewritten{SUCCESS older, CANCELLED newest}both classify FAILURE — the bug; both fixtures pin the fix.d8f54aa:a17e497→ SUCCESS, rewritten reverse-order → SUCCESS, only-CANCELLED → FAILURE, two-CANCELLED → FAILURE,{FAILURE older, CANCELLED newest}→ FAILURE. All five as specified.$passing): only-CANCELLED, two-CANCELLED, and FAILURE-older all wrongly read SUCCESS — the three guard fixtures catch it, per the test plan.$latest): thea17e497shape reads NONE, not SUCCESS — caught, per the test plan.Other ACs. The rewritten reverse-order fixture keeps its place with a comment explaining why a cancelled run that replaced nothing is not a verdict — rewritten, not deleted. Existing fixtures for ERROR/STALE/unknown-outcome blocking, NEUTRAL/SKIPPED passing, UNREADABLE, "a CANCELLED run blocks" (different context), both wind-down cases, and the in-flight cases are all present and pass unchanged. The comment block above
checks_statestates the carve-out and why it does not widen unknown-into-green ("discard needs a surviving verdict, never an empty context").changelog.d/139.mdfollows the fragment convention (<issue>.md, publishable prose, perchangelog.d/README.md).#133 overlap. #133 is still open; its hunks in
labels-reconcile.shsit at ~:175, ~:498, ~:581-614, ~:656-696 (release_shape_warning/reconcile_pr/main) and its test hunk at ~:92 — disjoint from this diff'schecks_state(:204-289) and test (:363-407) regions, as #139's Dependencies note predicted.git merge-treeof this head against current main: no conflicts. Whoever lands second rebases, but no same-line collision is expected.The standing
blocker:ci-redon this PR is the bug itself. On headd8f54aabthere is a reallabels / reconcilesuccess (12:35:37Z → 12:36:18Z) beside a later queue-cancelled zero-step duplicate (started == completed 12:41:53Z, run 30094035344) thatgh pr checkssurfaces as "fail". Under this PR's own rule the cancelled entry is discarded and the context reads SUCCESS; all other checks on the head are green. The label should clear on the next reconcile sweep.Non-blocking nit: with the survivor guard removed entirely (unconditional pre-sort drop), all five fixtures still classify identically — an emptied group collapses to a
nulloutcome, which is in neither$passingnor$waitingand still blocks. The guard is therefore defensive clarity rather than load-bearing, and it matches the issue's sketch verbatim; fine as shipped, no change requested.Round summary — handoff
One round, no fixes owed: codex (12:47), grok (12:48) and kimi (13:02) all approved head
d8f54aa— the head has not moved since ready-for-review. What was verified:checks_statenow discards aCANCELLEDentry inside a context group only when that group holds a non-cancelled sibling (a real verdict about the same bytes); a group whose entries are all cancelled still readsFAILURE— the unknown-as-green shape stays refused. The L366 fixture is rewritten with its reason, the three spec fixtures are in, full suite green.On the standing
blocker:ci-red— it is the #139 flap itself, manufactured on this PR's own head. The evidence, from the check-runs API ford8f54aa:labels / reconcile8948237802289483703568labels-reconcilegroup; run30094035344)Every real check on this head is green; the newest rollup entry is the cancelled duplicate, and main's pre-fix classifier reads newest-wins →
FAILURE. This PR is the fix for exactly that read, but the base branch governs its own checks.gh run rerunrefuses the cancelled run, and the documented empty-commit remedy (#139: "the only builder-available fix") would stale three current-head approvals to change zero bytes.So this handoff uses the designed path instead: the
state:needs-humanwrite below is the optimistic write BUILDER.md describes, and itslabeledevent wakes the sweep at this head — that run's ownlabels / reconcilecheck run supersedes the cancelled entry in the rollup, and the same sweep validates (or corrects) the label. If it corrects me, the completed run still heals the rollup and the next tick converges; no head move, no re-round either way.Requesting @danmt — over to you.