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
Stack merge hangs after partial merge invalidates latest-push approval #174
Expanded five-PR reproduction and server-state evidence: #172 (comment)
This reproduction retained APPROVED review decisions after GitHub rewrote the downstream heads, but the remaining queue entries were still removed. The failure is therefore broader than latest-push approval invalidation: the stack landing operation appears to lose downstream queue membership across the squash/restack boundary.
Controlled reproduction that isolates the variable: this is not approval invalidation. The downstream queue entries are evicted with approvals entirely disabled, and the eviction happens before the bottom PR merges — so it is also not the squash/restack rewrite I hypothesised in my earlier comment.
Layers below are labelled L1 (bottom, based on trunk) → L3 (tip).
Setup
Private repo, main protected by a ruleset with a merge_queue rule:
CI is a single no-op job on pull_request + merge_group (run: exit 0), so nothing here is CI latency or flake.
3-deep stack created with gh stack link l1 l2 l3 (gh 2.96.0, gh-stack v0.0.8), each layer touching a distinct file. All three CLEAN / MERGEABLE, all PR checks green. Enqueued once from the tip via Enqueue stack (3) → Confirm enqueue stack.
Observed
Polling pullRequest{state baseRefName isInMergeQueue mergeQueueEntry{position}} every ~15s:
21:19:42 L1 Q=1 L2 Q=2 L3 Q=3 <- all three admitted, correct order
21:19:58 L1 Q=1 L2 -- L3 -- <- L2, L3 evicted, 16s after enqueue
21:20:31 L1 MERGED, L2 base->main, L3 base=l2
21:23:17 ...still idle. Nothing in the queue, L2 and L3 open.
merge_group runs for the whole operation: exactly one, for L1.
L2 and L3 left the queue at 21:19:58. L1 did not merge until 21:20:31 — 33 seconds later. The entries are therefore not lost when downstream heads are rewritten; they are dropped on a validation pass while the bottom PR is still testing. The only distinguishing property at that moment is that their base ref is not the protected branch.
Recovery
After L1 merged, L2 was retargeted to main by GitHub and sat in a fully mergeable state:
UNPROCESSABLE: This pull request is part of a stack and must be merged
sequentially using the stack merge API.
That API is not in the public GraphQL schema (255 mutations, none stack-related), not in REST, and not in the gh-stack binary — consistent with what @grubmeshi reports in #268. So recovery from the stall is web-UI-only.
Second layer
Clicking Enqueue stack (2) on the same stack repeats it exactly:
Two layers landed, two merge_group runs, ~45s per layer with a no-op CI, one manual UI confirmation each.
Implication
The queue appears to admit every stack entry optimistically and then evict any entry whose base is not the protected branch, rather than losing entries across the merge boundary. If that is right, a stack can never be co-tested in a merge group under the current design, and the observable behaviour is one layer per manual UI interaction — an N-deep stack costs N clicks, N sequential queue passes, and N full CI runs, with no speculative execution across the stack.
Flagging the mechanism because this issue's title attributes the hang to latest-push approval, and #172 to the bottom-PR-only enqueue; both reproduce here with approvals switched off entirely, which suggests they are the same underlying behaviour.
Congratulations on officially becoming public preview! 🥳
I re-tested on July 30, 2026 with gh-stack v0.1.0, but the underlying queue eviction issue remains.
A three-PR stack was admitted in order. GitHub removed the two downstream entries before the bottom PR merged. After each squash merge it force-rewrote the remaining heads, but never requeued them. The stack required three gh stack merge invocations and three merge-group runs.
The rewritten PRs retained APPROVED/CLEAN, so fortunately the approval invalidation did not reproduce. v0.1.0 improves recovery through the new CLI command, but it still does not preserve downstream queue membership.
Another independent reproduction on public preview (repo: uber/submitqueue), and it lines up with @windsornguyen's controlled repro: this is not latest-push approval invalidation.
Symptom: enqueue a stack, the UI reports it enqueued, the bottom PR merges and every PR above it is silently left open. No error, no comment, nothing in the PR timeline indicating the upper entries were evicted.
Both approval-invalidation switches are off here, so a restack cannot be dismissing approvals — and the upper entries are dropped anyway. That's a further datapoint against the hypothesis in the title, alongside #172 and the ALLGREEN/SQUASH repro above. The current title ("invalidates latest-push approval") is probably misleading triage at this point.
The silent failure is the worst part operationally. There is no signal anywhere in the UI that entries left the queue — someone enqueues a stack, sees "enqueued", and it simply stops after the bottom PR lands. Even a timeline event on the evicted PRs would make this diagnosable.
This is literally making me merge one by one and when i have to bypass, i have to unstack and then merge and the whole rebase stuff kills me everytime :( the experience is miserable at the moment...
Hey all, we have a couple of fixes that are rolling out / actively being worked on related to this.
The first thing that's rolling out this week is a fix for queueing multiple stacked PRs at once. If a merge queue used squash merge, only the first PR would go through and other PRs would get kicked out of the queue due to falsely detected merge conflicts. We also released a fix that will properly rebase the remainder of a stack after a part of it is merged via a merge queue with squash.
In addition, we're working on an update to retain the approvals on upstack PRs after a partial stack merge. Approvals are retained when you click the "Rebase Stack" button in the UI, and the plan is to similarly retain approvals after merges.
Apologies for the slow comms here, but we do see these issues and we are working on it!
Reacted by Windsor Nguyễn, Imen Mahjoubi and Jesse Smith
@skarim Will those upcoming changes address this similar issue where stack merges are indefinitely hanging? #168 I have just encountered this issue, although I'm not sure if my problems were due to silent (slow) running actions workflows in the background which run on new commit (although I can't see any runs in the actions logs) or whether the merge was just hanging due to loss of approval during the merge/squash steps from lower PRs in the stack.
As far as I can see from the UI side, it was just stuck trying to squash both PRs into main, but nothing was happening. If I manually squash each PR one-by-one (and wait for the GHA workflow to re-run on every force-push when the base branch changes) then it worked.
Activity
windsornguyen commentedon Jul 12, 2026
Expanded five-PR reproduction and server-state evidence: #172 (comment)
This reproduction retained
APPROVEDreview decisions after GitHub rewrote the downstream heads, but the remaining queue entries were still removed. The failure is therefore broader than latest-push approval invalidation: the stack landing operation appears to lose downstream queue membership across the squash/restack boundary.windsornguyen commentedon Jul 27, 2026
Controlled reproduction that isolates the variable: this is not approval invalidation. The downstream queue entries are evicted with approvals entirely disabled, and the eviction happens before the bottom PR merges — so it is also not the squash/restack rewrite I hypothesised in my earlier comment.
Layers below are labelled L1 (bottom, based on trunk) → L3 (tip).
Setup
Private repo,
mainprotected by a ruleset with amerge_queuerule:CI is a single no-op job on
pull_request+merge_group(run: exit 0), so nothing here is CI latency or flake.3-deep stack created with
gh stack link l1 l2 l3(gh 2.96.0, gh-stack v0.0.8), each layer touching a distinct file. All threeCLEAN/MERGEABLE, all PR checks green. Enqueued once from the tip via Enqueue stack (3) → Confirm enqueue stack.Observed
Polling
pullRequest{state baseRefName isInMergeQueue mergeQueueEntry{position}}every ~15s:merge_groupruns for the whole operation: exactly one, for L1.L2 and L3 left the queue at 21:19:58. L1 did not merge until 21:20:31 — 33 seconds later. The entries are therefore not lost when downstream heads are rewritten; they are dropped on a validation pass while the bottom PR is still testing. The only distinguishing property at that moment is that their base ref is not the protected branch.
Recovery
After L1 merged, L2 was retargeted to
mainby GitHub and sat in a fully mergeable state:{"state":"OPEN","baseRefName":"main","mergeStateStatus":"CLEAN", "mergeable":"MERGEABLE","isInMergeQueue":false, "stack":{"size":3},"stackEntry":{"position":2}}enqueuePullRequeststill refuses it:That API is not in the public GraphQL schema (255 mutations, none stack-related), not in REST, and not in the
gh-stackbinary — consistent with what @grubmeshi reports in #268. So recovery from the stall is web-UI-only.Second layer
Clicking Enqueue stack (2) on the same stack repeats it exactly:
Two layers landed, two
merge_groupruns, ~45s per layer with a no-op CI, one manual UI confirmation each.Implication
The queue appears to admit every stack entry optimistically and then evict any entry whose base is not the protected branch, rather than losing entries across the merge boundary. If that is right, a stack can never be co-tested in a merge group under the current design, and the observable behaviour is one layer per manual UI interaction — an N-deep stack costs N clicks, N sequential queue passes, and N full CI runs, with no speculative execution across the stack.
Flagging the mechanism because this issue's title attributes the hang to latest-push approval, and #172 to the bottom-PR-only enqueue; both reproduce here with approvals switched off entirely, which suggests they are the same underlying behaviour.
windsornguyen commentedon Jul 30, 2026
Congratulations on officially becoming public preview! 🥳
I re-tested on July 30, 2026 with
gh-stackv0.1.0, but the underlying queue eviction issue remains.A three-PR stack was admitted in order. GitHub removed the two downstream entries before the bottom PR merged. After each squash merge it force-rewrote the remaining heads, but never requeued them. The stack required three
gh stack mergeinvocations and three merge-group runs.The rewritten PRs retained APPROVED/CLEAN, so fortunately the approval invalidation did not reproduce. v0.1.0 improves recovery through the new CLI command, but it still does not preserve downstream queue membership.
behinddwalls commentedon Jul 30, 2026
Another independent reproduction on public preview (repo:
uber/submitqueue), and it lines up with @windsornguyen's controlled repro: this is not latest-push approval invalidation.Symptom: enqueue a stack, the UI reports it enqueued, the bottom PR merges and every PR above it is silently left open. No error, no comment, nothing in the PR timeline indicating the upper entries were evicted.
Trunk ruleset:
Both approval-invalidation switches are off here, so a restack cannot be dismissing approvals — and the upper entries are dropped anyway. That's a further datapoint against the hypothesis in the title, alongside #172 and the ALLGREEN/SQUASH repro above. The current title ("invalidates latest-push approval") is probably misleading triage at this point.
The silent failure is the worst part operationally. There is no signal anywhere in the UI that entries left the queue — someone enqueues a stack, sees "enqueued", and it simply stops after the bottom PR lands. Even a timeline event on the evicted PRs would make this diagnosable.
windsornguyen commentedon Aug 7, 2026
Hi @skarim, any updates on this one?
behinddwalls commentedon Aug 7, 2026
This is literally making me merge one by one and when i have to bypass, i have to unstack and then merge and the whole rebase stuff kills me everytime :( the experience is miserable at the moment...
skarim commentedon Aug 11, 2026
Hey all, we have a couple of fixes that are rolling out / actively being worked on related to this.
The first thing that's rolling out this week is a fix for queueing multiple stacked PRs at once. If a merge queue used squash merge, only the first PR would go through and other PRs would get kicked out of the queue due to falsely detected merge conflicts. We also released a fix that will properly rebase the remainder of a stack after a part of it is merged via a merge queue with squash.
In addition, we're working on an update to retain the approvals on upstack PRs after a partial stack merge. Approvals are retained when you click the "Rebase Stack" button in the UI, and the plan is to similarly retain approvals after merges.
Apologies for the slow comms here, but we do see these issues and we are working on it!
windsornguyen commentedon Aug 11, 2026
@skarim Thank you for the update!
callumnewlands commentedon Sep 2, 2026
@skarim Will those upcoming changes address this similar issue where stack merges are indefinitely hanging? #168 I have just encountered this issue, although I'm not sure if my problems were due to silent (slow) running actions workflows in the background which run on new commit (although I can't see any runs in the actions logs) or whether the merge was just hanging due to loss of approval during the merge/squash steps from lower PRs in the stack.
As far as I can see from the UI side, it was just stuck trying to squash both PRs into main, but nothing was happening. If I manually squash each PR one-by-one (and wait for the GHA workflow to re-run on every force-push when the base branch changes) then it worked.