You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(pr-pipeline): re-enter the merge queue after a cancelled or flaky merge_group run at an unchanged head #8566
When a merge_group run is cancelled or fails on a known flake, GitHub removes the pull request from the merge queue (RemovedFromMergeQueueEvent) and nothing puts it back. On 2026-10-20 #8110 left on a flaky failed check and #8563 on a cancel at 08:00Z; #8563 returned only because a session watched the queue by hand.
Two populations differ:
Lane PRs (pr-merge's own candidates): confirm whether the next pr-merge sweep re-gates and re-enqueues an ejected PR at an unchanged head, or whether a stored decision (skip-unchanged keys, the in-flight or ejection guard) keeps it out. If it stays out, re-enqueue it when the last removal reason is a cancel or a known-flake failure, capped by the existing per-head ejection limit.
PRs merged outside pr-merge (session or owner guarded merges): pr-merge does not merge these, so re-queuing them from pr-merge would widen what it merges unattended. That needs an owner decision; until then the merging session watches its own queue entry.
Done when
A lane PR ejected by a cancelled merge_group run at an unchanged head is back in the queue within one sweep, capped per head; a test covers cancel → re-enqueue and a real failure → no re-enqueue. Population 2 is either decided by the owner or explicitly left to the merging session in the docs.
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Medium
Complexity: Medium
Uncertainty: Knowable
Flags: none
Blocked by: none
Decision recorded. The owner answered in the Refinement session (2026-10-25), deciding the open items below on the facts:
go with whatever the facts/data/metrics + /discovery:research says and ensure we're aligned with SOLID, DRY/SSOT principles
I should only ever be in the loop ONLY when necessary (intent/vision changed, something came up during implementation that impacts trajectory/alignment, OR a high security risk) - I've covered this all before
[quote truncated; the full decision is at the link]
The decision settles the open questions: re-aim the lane-PR half at the enable-auto-merge job, count only failure runs toward the gated-PR cap with a separate small cancel cap, and document that pr-merge does not re-queue PRs merged outside it. Whether a scheduled run already re-arms a removed lane PR is the first thing to check during implementation, so I left it as Knowable rather than a question.
Part 1 findings (read at origin/main, 2026-10-20):
Lane PRs are already re-enqueued. An ejected lane PR at an unchanged head is a candidate on the next sweep (pr-merge.yml:541-553; the prefilter drops only holds, babysit_merge_prefilter.py:36-44). Skip-unchanged stores refusals only (babysit_merge_skip.py:75-86, :652-655), and pr-merge keeps no "already enqueued" state (no --state-dir).
The gap is the cap._failed_ejections counts every removal whose reason is failed_checks (babysit_merge.py:2095), and GitHub reports a cancelled merge_group run with that same reason (fix(guardrails): cap the git alias walk's nesting depth to the stack so it denies instead of crashing #8563, run 39389356196, cancelled, recorded as failed_checks). With max_queue_ejections: 2 (babysit_merge_policy.py:472), any ejection, cancel or real failure, gets exactly one retry.
Smallest change if wanted: pair each removal with the PR's newest merge_group run by time (SHA pairing does not match), charge the cap only for failure runs, and cap all removals (cancels included) at a separate small limit. Flakes are indistinguishable from real failures by either signal; they need a known-flake list or the one retry the cap already gives. Value is modest, since lane PRs already get a retry; parked below the throughput work until someone hits the cap on a cancel.
Decision recorded. The owner answered in the poteto-review session (2026-10-20, question dialog, "Accept all"): part 2 is a no. It had been the session's default, not an owner answer; it is now the owner's.
Released for re-triage under the owner's 2026-10-24 rule: an issue stops for a person only for an intent or vision question, a security call, a host or attended run, or spend outside prepaid credit. The hold label came off and intake triage runs again now; it re-applies a hold, with a numbered question, when one of those applies.
To hold this issue again, add needs-human (or status: needs-decision) and say why in a comment.
Not admitted to the issue lane: no trusted-human acceptance criteria: a listed human must put the confirmed - [ ] criteria in the body or post them as their own comment, or, on an issue a listed human filed, intake's newest refinement comment must hold them unedited. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).
melodic-automation-lanes commented on Oct 25, 2026
Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.
Intended outcome: A cancelled merge_group run no longer spends the failed-check ejection cap, so a gated PR ejected by a cancel at an unchanged head is re-enqueued, with tests for cancel to re-enqueue and real failure to no re-enqueue, and docs saying merging sessions watch their own queue entry.
The lane-PR half assumed the gate lists lane PRs. Lane PRs now merge through native auto-merge and the gate never lists them. Should this be re-aimed at the enable-auto-merge job (re-arm after a cancel, capped per head), at the gated owner and Dependabot PRs only, or closed as superseded?
RECOMMENDED: Re-aim the lane-PR half at the enable-auto-merge job and keep the cap change for gated PRs as a separate item. Basis: .base/docs/conventions/pr-pipeline.yaml:881 sets lane-prs: native; .base/.github/workflows/pr-merge.yml:613 says native lane PRs are never read by the gate; candidates 9448, 9508
Does GitHub's native auto-merge stay armed or re-arm after the merge queue removes a PR for a cancelled run? That decides whether lane PRs need any change.
RECOMMENDED: Unknown; check the enable-auto-merge job's disarm and re-arm behavior in .github/scripts/pr-merge/enable-auto-merge.mjs before writing criteria. Basis: judgment
For gated PRs, should the cap at babysit_merge.py:4225 count only failure runs and give cancels a separate small limit, as the triage comment proposes?
RECOMMENDED: Yes. Charge the cap only for failure runs, and cap cancels at a separate small limit such as 2. Basis: .base/plugins/source-control/skills/babysit-prs/scripts/babysit_merge.py:2179 and :4225
Sources read
context.json: owner decision comment says part 2 (PRs merged outside pr-merge) is a no
context.json: Part 1 findings comment says the cap counts cancels as failed_checks and cites run 39389356196
/home/runner/work/claude-code-plugins/claude-code-plugins/.base/docs/conventions/pr-pipeline.yaml:881: lane-prs is native, and lane PRs are not listed by the gate
/home/runner/work/claude-code-plugins/claude-code-plugins/.base/.github/workflows/pr-merge.yml:613: lanes App PRs are listed only under lane-prs gate; under native they merge via enable-auto-merge
/home/runner/work/claude-code-plugins/claude-code-plugins/.base/plugins/source-control/skills/babysit-prs/scripts/babysit_merge.py:2179: _failed_ejections counts removals for failed checks since the newest head change
/home/runner/work/claude-code-plugins/claude-code-plugins/.base/plugins/source-control/skills/babysit-prs/scripts/babysit_merge.py:4225: the gate holds the PR once ejections reach max_queue_ejections
Automated refinement by the lanes App. Answer with a comment whose first line is **Decision recorded.**.
Decision recorded. The owner answered in the Refinement session (2026-10-25), deciding the open items below on the facts:
go with whatever the facts/data/metrics + /discovery:research says and ensure we're aligned with SOLID, DRY/SSOT principles
I should only ever be in the loop ONLY when necessary (intent/vision changed, something came up during implementation that impacts trajectory/alignment, OR a high security risk) - I've covered this all before
Question asked: Re-aim the lane-PR half at the enable-auto-merge job, and give gated PRs' cancels their own small cap?
Already decided: #8566 (comment) (2026-10-20: part 2, re-queuing PRs merged outside pr-merge, is a no)
Lane PRs now merge through native auto-merge, and the gate never lists them. Re-aim the lane-PR half at the enable-auto-merge job, aim it at gated PRs only, or close it as superseded?
Decided: Re-aim it at the enable-auto-merge job. enable-auto-merge.mjs has no ejection count, and only scheduled or hand-started runs arm. The first step is to find whether a scheduled run re-arms a lane PR the queue removed. If it does, add the per-head cap there; if not, add the re-arm. Whether GitHub keeps auto-merge armed after a queue removal is UNVERIFIED. Basis: docs/conventions/pr-pipeline.yaml:882 (lane-prs: native), .github/workflows/pr-merge.yml:623-624, .github/scripts/pr-merge/enable-auto-merge.mjs:40-45 on origin/main 4ab8989 (kept; added the missing ejection count).
For gated PRs, should the cap count only failure runs and give cancels a separate small limit?
Decided: Yes. Charge the cap only for failure runs, and cap cancels at a separate small limit such as 2, the current max_queue_ejections default. Basis: plugins/source-control/skills/babysit-prs/scripts/babysit_merge.py:2179-2201 (counts every failed_checks removal) and :4220; babysit_merge_policy.py:575 (kept; the refinement cited line 4225).
Acceptance criteria (converted from the body's "Done when" paragraph, wording unchanged except the population-2 line, which your 2026-10-20 answer settles, and the added cap line):
A lane PR ejected by a cancelled merge_group run at an unchanged head is back in the queue within one sweep, capped per head.
A test covers cancel → re-enqueue and a real failure → no re-enqueue.
For gated PRs, the ejection cap counts only failure runs, and cancels have their own small cap.
The docs say pr-merge does not re-queue a PR merged outside it; the merging session watches its own queue entry.
Criteria 1-3 met on main (cancels count against merge.max-queue-ejections; lane PRs re-arm under that cap). Criterion 4 superseded by Q-REQ (a): pr-merge re-enqueues a person's ejected PR under the same cap.
Activity
github-actions commentedon Oct 20, 2026
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Medium
Complexity: Medium
Uncertainty: Knowable
Flags: none
Blocked by: none
#8566 (comment)
The decision settles the open questions: re-aim the lane-PR half at the enable-auto-merge job, count only
failureruns toward the gated-PR cap with a separate small cancel cap, and document that pr-merge does not re-queue PRs merged outside it. Whether a scheduled run already re-arms a removed lane PR is the first thing to check during implementation, so I left it as Knowable rather than a question.Automated triage, applied by the lanes App.
kyle-sexton commentedon Oct 20, 2026
Part 1 findings (read at origin/main, 2026-10-20):
pr-merge.yml:541-553; the prefilter drops only holds,babysit_merge_prefilter.py:36-44). Skip-unchanged stores refusals only (babysit_merge_skip.py:75-86,:652-655), and pr-merge keeps no "already enqueued" state (no--state-dir)._failed_ejectionscounts every removal whose reason isfailed_checks(babysit_merge.py:2095), and GitHub reports a cancelledmerge_grouprun with that same reason (fix(guardrails): cap the git alias walk's nesting depth to the stack so it denies instead of crashing #8563, run 39389356196,cancelled, recorded asfailed_checks). Withmax_queue_ejections: 2(babysit_merge_policy.py:472), any ejection, cancel or real failure, gets exactly one retry.needs-human, so pr-merge never listed either.Smallest change if wanted: pair each removal with the PR's newest
merge_grouprun by time (SHA pairing does not match), charge the cap only forfailureruns, and cap all removals (cancels included) at a separate small limit. Flakes are indistinguishable from real failures by either signal; they need a known-flake list or the one retry the cap already gives. Value is modest, since lane PRs already get a retry; parked below the throughput work until someone hits the cap on a cancel.kyle-sexton commentedon Oct 20, 2026
Decision recorded. The owner answered in the poteto-review session (2026-10-20, question dialog, "Accept all"): part 2 is a no. It had been the session's default, not an owner answer; it is now the owner's.
kyle-sexton commentedon Oct 25, 2026
Released for re-triage under the owner's 2026-10-24 rule: an issue stops for a person only for an intent or vision question, a security call, a host or attended run, or spend outside prepaid credit. The hold label came off and intake triage runs again now; it re-applies a hold, with a numbered question, when one of those applies.
To hold this issue again, add
needs-human(orstatus: needs-decision) and say why in a comment.melodic-automation-lanes commentedon Oct 25, 2026
Not admitted to the issue lane: no trusted-human acceptance criteria: a listed human must put the confirmed
- [ ]criteria in the body or post them as their own comment, or, on an issue a listed human filed, intake's newest refinement comment must hold them unedited. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).melodic-automation-lanes commentedon Oct 25, 2026
The latest triage verdict holds nothing: the issue is at
status: ready.melodic-automation-lanes commentedon Oct 25, 2026
Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.
Intended outcome: A cancelled merge_group run no longer spends the failed-check ejection cap, so a gated PR ejected by a cancel at an unchanged head is re-enqueued, with tests for cancel to re-enqueue and real failure to no re-enqueue, and docs saying merging sessions watch their own queue entry.
Related work
Open questions
RECOMMENDED: Re-aim the lane-PR half at the enable-auto-merge job and keep the cap change for gated PRs as a separate item. Basis: .base/docs/conventions/pr-pipeline.yaml:881 sets lane-prs: native; .base/.github/workflows/pr-merge.yml:613 says native lane PRs are never read by the gate; candidates 9448, 9508
RECOMMENDED: Unknown; check the enable-auto-merge job's disarm and re-arm behavior in .github/scripts/pr-merge/enable-auto-merge.mjs before writing criteria. Basis: judgment
RECOMMENDED: Yes. Charge the cap only for failure runs, and cap cancels at a separate small limit such as 2. Basis: .base/plugins/source-control/skills/babysit-prs/scripts/babysit_merge.py:2179 and :4225
Sources read
Automated refinement by the lanes App. Answer with a comment whose first line is
**Decision recorded.**.kyle-sexton commentedon Oct 26, 2026
Decision recorded. The owner answered in the Refinement session (2026-10-25), deciding the open items below on the facts:
Question asked: Re-aim the lane-PR half at the enable-auto-merge job, and give gated PRs' cancels their own small cap?
Already decided: #8566 (comment) (2026-10-20: part 2, re-queuing PRs merged outside pr-merge, is a no)
Decided: Re-aim it at the enable-auto-merge job. enable-auto-merge.mjs has no ejection count, and only scheduled or hand-started runs arm. The first step is to find whether a scheduled run re-arms a lane PR the queue removed. If it does, add the per-head cap there; if not, add the re-arm. Whether GitHub keeps auto-merge armed after a queue removal is UNVERIFIED. Basis:
docs/conventions/pr-pipeline.yaml:882(lane-prs: native),.github/workflows/pr-merge.yml:623-624,.github/scripts/pr-merge/enable-auto-merge.mjs:40-45on origin/main 4ab8989 (kept; added the missing ejection count).failureruns and give cancels a separate small limit?Decided: Yes. Charge the cap only for
failureruns, and cap cancels at a separate small limit such as 2, the currentmax_queue_ejectionsdefault. Basis:plugins/source-control/skills/babysit-prs/scripts/babysit_merge.py:2179-2201(counts everyfailed_checksremoval) and:4220;babysit_merge_policy.py:575(kept; the refinement cited line 4225).Acceptance criteria (converted from the body's "Done when" paragraph, wording unchanged except the population-2 line, which your 2026-10-20 answer settles, and the added cap line):
merge_grouprun at an unchanged head is back in the queue within one sweep, capped per head.failureruns, and cancels have their own small cap.kyle-sexton commentedon Oct 26, 2026
Criteria 1-3 met on main (cancels count against merge.max-queue-ejections; lane PRs re-arm under that cap). Criterion 4 superseded by Q-REQ (a): pr-merge re-enqueues a person's ejected PR under the same cap.