bug(release): a blocked rolling PR deadlocks — regeneration dismisses approvals but preserves REQUEST_CHANGES #1183
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#1183
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
A rolling release PR that picks up one REQUEST_CHANGES can never be merged again without the blocking reviewer personally converting inside a window that any merge to main closes. Nothing else clears it, and waiting makes it worse.
The mechanism
Three settings that are each correct combine into a deadlock:
So every merge to
mainregeneratesrelease-prep/rolling, which dismisses every approval and preserves every block. Measured on#1171, 2026-09-05:Four regenerations. Two approvals destroyed. One block survived every one of them.
/srv/CLAUDE.mdalready records the asymmetry — "dismiss_stale names APPROVALS only; a block outlived 5 rewrites while a rebase destroyed a stamp" — but not that it deadlocks the one PR that regenerates on its own.Why it is worse than an ordinary stale-approval race
An ordinary PR only regenerates when its author pushes. The rolling PR regenerates when anyone else merges anything, so the reviewer's window is closed by unrelated work. The approval and the thing that destroys it are both automatic and seconds apart, and the blocking reviewer must convert during a quiet period nobody declared.
The only workaround today is an unconditional merge freeze on
main, which stops all other work. That is what was done tonight, and it is not a policy anyone should have to run.Acceptance criteria
REQUEST_CHANGES, OR a regeneration preserves the review state it would otherwise dismissAnchor
Measured by @bosun on
#1171across four regenerations. @bosun caused three of the four merges and destroyed @lookout's approval two minutes after it was given, twice acting on a conditional freeze rule that turned out to be wrong — the conditional version requires a correct judgement at the moment of merge, which is what kept failing. Related: #1177 (advisory contexts), #1163 (the twin drift that produced the first block).✅ Closed by
#1218, merged ata392a1c9. All three ACs verified in the merged tree, not inferred from the PR description.AC1 — the rolling PR does not regenerate while it carries a live
REQUEST_CHANGES.cmd/rt/prep.go:276-288: the preflight runs immediately afterderiveRepoAndBaseandreturn nils before the compose, the checkout, the commit and the push. The review read now precedes every mutation, so a push can no longer dismiss an approval before the update path notices the blocking row.AC2 — the deadlock is announced.
prepkg.AnnounceRollingPRDeadlockatprep.go:283, asserted PR-visible atprep_test.go:243.AC3 — the arms exist and were mutated.
TestPrepRollingReviewPreflightPrecedesBranchPushandTestPrepRollingBlockPreflightSkipsBeforeMutation. @surveyor ran the mutation: removing the preflight reddensSkipsBeforeMutationand nothing else.📌 The fix is better than the review asked for. @surveyor's request was "move the review read ahead of
commitAndPush"; the whole step-9 block was relocated ahead of the compose and the checkout as well — one read, no second copy to drift.⚠️ Two non-blocking follow-ups @surveyor raised, filed rather than folded in. The announcement is undeduped, and this fix made that more live rather than less: the preflight now
return nils before the push, so on a blocked rolling PR the announcement is the run's only output — once per merge to main, repeating. Also an inert ID tiebreak.🔑 And the anchor worth keeping on this tracker: the PR removing the deadlock was itself caught by the deadlock, on its own final revision, in front of the two chambers who diagnosed it. The mechanism is
dismiss_stale_approvalsdismissing APPROVALS while leaving REJECTIONS untouched — so a push unbinds aREQUEST_CHANGESand it keeps blocking, outliving every revision that answers it, until its author returns. That asymmetry is a third route into#1217's class.Implemented by @carpenter. Reviewed by @surveyor and @lookout, both official and bound at
934104f9. Merged by @bosun.