Approved, green PRs are orphaned when their worker exits before auto-merge can succeed: nothing retries the merge - #2036
Merged
Conversation
…s owns the merge
The worker-less merge retry (bd-a370ak) gave up the moment it saw a red
pipeline: it paged "approved merge abandoned" and latched the stamp, so
nothing re-armed it when a later pipeline went green (emricare/tonic !292
sat green and unmerged on v0.1.72). It had also been started beside a live
`<task>:fixpass` worker, because live_merge_owner/1 only looked at the
task's own registry key.
- detached_ci_red/1: red CI now waits like draft / CI pending, bounded by
max_retry_wait_ms from the stamp's `since`; the coordinator gets one
merge_blocked(:ci_failed) notice per pending merge, latched on the stamp
(PendingMerge.note_block/2) so restarts and re-armed retries don't repeat it.
- live_merge_owner/1 returns {:subordinate, key} for an active :fixpass /
:conflict pass (Worker.active_subordinate/1); the sweeper skips it and a
running retry stands down.
- W21 registry row and guard-policy doc updated; citations re-anchored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ryanrborn
deleted the
bugfix/2002-approved-green-prs-are-orphaned-when
branch
September 24, 2026 13:35
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.
Summary
This is round 2 for the orphaned approved-merge fix. The first version ([bd-a370ak]) shipped in v0.1.72 and failed post-merge verification on emricare/tonic !292 and !293. The sweeper did detect both orphaned merges. The worker-less retry then gave up on the first red pipeline: it paged "approved merge abandoned (orphaned PR: ci_failed)" and latched the stamp as escalated. Nothing re-armed it, so when a later pipeline went green, !292 sat green and mergeable until a human resumed the worker.
There were two defects, both confirmed from the live DB (
worker_runsandmessages):detached_outcome/3calledgive_up_retry(state, :ci_failed). Nowdetached_ci_red/1waits on red CI the same way it waits on a draft or on pending CI, and it merges once a re-run or a new pipeline goes green.max_retry_wait_ms(48h), measured from the stamp'ssince. After that it pages once, aswait_exhausted, and latches.merge_blocked(:ci_failed)notice per pending merge, not an "abandoned" page. The notice is latched on the stamp through the newPendingMerge.note_block/2(notified_block), so a server restart or a re-armed retry doesn't repeat it. A fresh stamp from a live Watchdog starts a new episode.<task>:fixpasswas still working the red pipeline: acqboi's fix pass ended 13s after the give-up, and 2oyzcn's ran for another ~3 min.live_merge_owner/1only checked the task's own registry key. It now returns{:subordinate, key}for any active:fixpassor:conflictpass, found through the newWorker.active_subordinate/1, which reuses the single-active-worker guard's probe. When that happens the sweeper skips the task and a running retry stands down. Once the pass exits, a later sweep re-arms the retry.The merge guards on the retry path are unchanged. A head pushed after the approval (for example by a fix pass) still has to pass the stale-reviewed-SHA and coverage decision, and the zero-net-diff guard, before anything merges.
On the GitLab
awaiting_approvalquestion: that status comes from the MergeQueue's own item after it adopts the MR.advance_ready_ladder/3reads only the forge'sapprovedflag, which an in-process ReviewGate approval never sets, so on a ReviewGate lane the item idles atawaiting_approvalby design, and the Watchdog or the retry does the merge. GitLab approval is not required on tonic: !292 was merged by the Watchdog at 03:37Z without a GitLab approval. If a project did require one, the retry would seeblock_reason: :needs_approvalor:needs_nonauthor_approvaland page once. It would not merge.W21 in the guard registry and in
docs/review-coverage-and-guard-policy.mdnow names the new sites. I re-anchored the doc'swatchdog.excitations with an exact line remap.Post-deploy verification (bd-a370ak,
verify_after_deploy)After the restart, what to look for:
<task> merge blocked — required CI checks are failing. It should not getapproved merge abandoned (orphaned PR: ci_failed).issues.pending_mergeJSON has"notified_block": "ci_failed","reason": "ci_failed", and"escalated_at": null. The retry is still running, soWatchdog.retry_whereis(task_id)returns a pid.merge_retry auto-merged orphaned approved MR … (pinned to <reviewed sha>)within one retry interval (2 min). The task then closes or parks at awaiting-verification.<task>:fixpassor<task>:conflictworker is running, the sweeper does not logstarted a worker-less retryfor that task.A working result means an approved PR that goes red and later green, for example after an infra fix on main plus a pipeline re-run, merges by itself without anyone resuming the worker.
Test plan
apps/arbiter/test/arbiter/workflows/pending_merge_sweeper_test.exs: I wrote them first, and all 6 failed against the old code:stale_reviewed_shamax_wait_ms, aswait_exhausted:fixpassblocks the sweeper (:live_worker); a:conflictpass that starts mid-retry makes the retry stand down without mergingwatchdog*_test.exs,worker_test.exs,merge_queue_test.exs,concurrent_task_workers_test.exs,merged_pr_finalizer*_test.exs,review_coverage_design_test.exs,guard_registry_test.exs,circuit_breaker_adoption_test.exsmix precommitexits 0: 7398 + 1711 + 887 tests, 0 failures. I ran it with the worker'sARB_*env vars unset, which the known ProvisioningTest environment failures need.mix format --check-formattedis clean, andmix credo --stricton the changed files is cleanmix dialyzer: its only warning isarbiter_cli/cmd/doctor/checks.ex:177. This branch doesn't touch that file, and.dialyzer_ignore.exsdocuments the warning as depending on how the repo was cloned.StubMerger: while red, the stamp hadnotified_block: "ci_failed",escalated_at: nil, and the retry was alive. After green, the task was:closedwith merge{"!292", <reviewed sha>}and exactly one "merge blocked — required CI checks are failing" message.References
worker_env; delete ConfigDir's old fallbacks #1947, agy parity T7 — agy workers can't resume, be nudged, or auto-resume: implementsplice_prompt/2#1966, worker_list reports zero workers while a worker is actively running, leading the coordinator to stop live work #1932Closes #2002
🤖 Generated with Claude Code