ci(unwedge): rule 3 — a merge_group run whose queue group is gone is cancelled; arming refuses --auto unless guard-tree is green on the current head (#3292, #3358) - #3403
Merged
Conversation
…roup is gone Run 35099392561 built gh-readonly-queue/main/pr-3266-2261757fd8a4... from 13:02:23Z to 14:30:59Z -- eighty-eight minutes -- after the queue had already discarded that entry and rebuilt #3266 on base f5d02de. GitHub does not cancel the run on a discarded ref: it keeps building, draws runners, and then holds `gate` queued for fifty minutes on a concurrency group, computing a verdict nothing can ever read. THE KEY IS (PR NUMBER, BASE OID), AND BOTH HALVES ARE LOAD-BEARING. A merge queue ref is `gh-readonly-queue/<base>/pr-<N>-<40 hex>` and the 40 hex IS the entry's `baseCommit.oid` -- read live from both sides and committed as the fixture (o1_mq_entries_live.json is the verbatim answer to MQ_QUERY, so the field names in the predicate cannot drift from the ones GitHub returns). Matching on the PR number alone answers LEAVE on the one run this rule exists to cancel -- the zombie and its replacement share the number (row O5). Matching on the base alone reads one group as licence to judge its neighbour: #3266's base oid IS #3270's head oid. NOT REDUNDANT WITH THE DEAD-REF PASS. That pass asks whether the git ref resolves; this asks whether the GROUP is still an entry of the live queue. They disagree in both directions -- a discarded group's ref can linger, a live group's ref can 404 mid-rebuild. ABSENCE OF EVIDENCE IS NOT ABSENCE OF THE GROUP. Unreadable, wrong-shape, and EMPTY entries all REFUSE. Empty is the dangerous one: read as "the queue is empty" it licenses cancelling every merge_group run in flight -- the whole queue, in one sweep (rows O3/O3b/O3c). Verified live, against a freshly fetched payload rather than the fixture: pr-3270-eb262f8e -> LEAVE (entry at position 1) pr-3266-f5d02deb -> LEAVE (entry at position 2) pr-3266-2261757f -> CANCEL (the measured zombie) pr-9999-eb262f8e -> CANCEL (a live base, a PR not in the queue at all) Mutation proof: disabling the absence check turns O2 red (self-test exit 1, "got LEAVE, wanted CANCEL"); restored, exit 0. 53 rows, 0 red. Pmat-Ticket: PMAT-3292 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ad (#3292) #3278 was armed on 2026-09-16 while its guard-tree was RED. The merge queue took it, put it at POSITION 1, and when it failed there it ejected the batch queued behind it. An arming decision is not local: a PR armed on a red guard costs every PR behind it a rebuild, and the queue is deepest exactly when that hurts most. THE STALE-CHECK TRAP IS THE WHOLE POINT. "guard-tree is green on this PR" is a claim about a SHA, not about a PR, and every name-keyed surface (`gh pr checks`, `statusCheckRollup`) will show a success from two pushes ago next to the newer head. So the head sha is read FIRST, the checks come from repos/{owner}/{repo}/commits/<that sha>/check-runs -- an endpoint that cannot answer about another commit -- and every row is filtered on `head_sha == <that sha>` again anyway. A success that exists only on an older sha is the named state `stale`, not a silent `absent`: the two want different fixes. ABSENT IS A REFUSAL. No row, no failure, arm -- that is the fail-open shape this repo keeps shipping. Here it exits 3, as do in_progress, queued, skipped, and a re-run in flight beside an earlier success on the same sha. WHY NOT pr_review_quorum_arm.sh. It was searched for first and it IS an arming path, but it evaluates S13's review-quorum predicate and refuses with Q1 when there is no signed review receipt, so it cannot be the general arming entry point; and it reads statusCheckRollup, the surface this trap lives in. The two compose -- a quorum PERMIT still has to clear this precondition. 12 rows, 0 red, fixtures only, with A1 as the positive control (without it a function that always refuses satisfies every other row). Verified live in all three polarities against open PRs, --dry-run: #3366 refuse: guard-tree is failure on 179c69b... exit 3 #3361 refuse: guard-tree is in_progress on 062e471... exit 3 #3363 WOULD-ARM on e5cf234... exit 0 #99999999 (no such PR) exit 2 bashrs: 0 errors. Pmat-Ticket: PMAT-3292 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
§13.11 rung 1 — quorum shadow verdict Shadow mode: this records a verdict and merges nothing. A refusal |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #3361 (rules 1+2, merged 17:33Z). Operator rulings 2026-09-16.
Rule 3 — orphaned
merge_grouprun. Amerge_groupCI run whose(PR number, base oid)is absent from the livemergeQueue.entriesis a zombie: measured today, run 35099392561 forpr-3266-2261757f…kept running and then heldgatequeued for 50 min after the queue had rebuilt that entry aspr-3266-f5d02deb….orphan_verdict EVENT BRANCH ENTRIES_FILE→ CANCEL only when the group is provably gone; LEAVE when present; REFUSE when the entries list is unreadable, wrong-shaped, or empty (an empty read would license cancelling the whole queue). Keyed on PR number and base oid because the zombie and its replacement share the PR number, and #3266's base oid is #3270's head oid. Rows O0a–O7 (13), fixtureo1_mq_entries_live.jsonis a verbatim graphql answer; proved live against a fresh payload: the measured zombie → CANCEL, both live groups → LEAVE. Mutation: absence check disabled → O2 RED (exit 1), restored → 0.Arming precondition —
scripts/arm_pr_automerge.sh PR. Today #3278 was armed while its guard-tree was red, sat at queue position 1, and ejected the batch behind it. The helper reads the PR's current head, reads the check runs for that sha (neverstatusCheckRollup, the name-keyed surface the stale-check trap lives in), and runsgh pr merge --auto --squashonly ifguard-treeissuccesson that exact sha; otherwiserefuse: guard-tree is <state> on <sha>, exit 3. Rows A1–A12 incl. success-on-an-older-sha-only → refuse. Proved live in all three polarities on #3366 (failure), #3361 (in_progress), #3363 (would-arm); used to arm #3396.Self-tests: unwedge 53 rows ok, arm 12 rows ok; bashrs 0 errors on both scripts (warnings are the pre-existing SC2086 class). No runners API anywhere; capacity stays the
fleet-bin.shoracle.🤖 Generated with Claude Code