Stacked PR keeps a stale head after its branch is rewritten server-side: merge box spins on "loading stack status", and both update-branch and merge refuse
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- git, github, go, graphql
- Domain
- backend-api-design, cli
Research direction
Trace stack status handling and the update-branch, merge, and merge-async entry points, using the reported branch-ref and pull-request-head mismatch as the starting case. Reproduce the server-side rewrite followed by the stale stacked pull request, then verify that the head follows the branch or that a supported recovery route remains available. Confirm that the UI leaves the loading state and that merging reports its completed result.
Written by the indexing model from the issue text.
Description
Summary
A stacked pull request got its branch rewritten by a GitHub-side operation, but the pull request head never followed. The branch ref and refs/pull/<n>/head then pointed at different commits, and the pull request was wedged: the merge box spun on loading stack status indefinitely, mergeable stayed UNKNOWN, and both REST routes out of the state were refused because the pull request is stacked.
Environment
- Native stack feature (stack created in the web UI, not via
gh stack). - Private organization repository.
- Default branch protected by classic branch protection: 6 required status checks (GitHub Actions), strict (branch must be up to date). No rulesets, no merge queue, no required reviews, no CODEOWNERS.
- Stack has 2 entries, base = default branch. Position 1 was merged about four weeks earlier; position 2 stayed open. The stack still reports
"open": trueand still lists the merged pull request as a member. - Both stack members target the default branch directly (
base.ref= default branch for each).
Reproduction
- Create a 2-entry stack on the default branch.
- Merge the bottom pull request. The stack stays
openand keeps the merged pull request as position 1. - Let the default branch advance by several commits over the following weeks.
- Have the remaining branch rewritten through GitHub — the resulting commits carry committer
GitHub, so the rewrite happened server-side, not from a local push.
Observed
After step 4 the branch ref and the pull request disagreed, and stayed that way for ~15 hours until manually unstuck:
refs/heads/<branch> = B rewritten, committer "GitHub", day 2 15:38
refs/pull/<n>/head = A pre-rewrite, committer "GitHub", day 2 01:10
GET /repos/{o}/{r}/pulls/{n} -> head.sha = A
default branch --o--o--o--o--o--o--o (advanced while the PR sat open)
\
refs/pull/<n>/head *A <- what the PR, the API and the merge box see
\
refs/heads/<branch> *B <- what the server-side rewrite actually wrote
The pull request was unmergeable from every direction:
- UI: merge box stuck on loading stack status; the spinner never resolved. No Merge button, no Update branch button, no error text.
- REST / GraphQL:
mergeable: null/UNKNOWN,mergeable_state: "unknown",mergeStateStatus: UNKNOWN— stable across repeated polls spread over many minutes. (Polling normally forces the background recomputation; here it never converged.) statusCheckRollupreportedSUCCESS— but for the stale headA.
Both REST escape hatches refused, each citing the stack:
PUT /repos/{owner}/{repo}/pulls/{n}/update-branch
403 {"message": "Updating a stacked PR's branch via this endpoint is not supported."}
PUT /repos/{owner}/{repo}/pulls/{n}/merge
403 {"message": "Merging stacked PRs via this endpoint is not supported. Use the asynchronous merge endpoint instead."}
The asynchronous endpoint that the second message points to accepted the request and then failed against the stale head:
PUT /repos/{owner}/{repo}/pulls/{n}/merge-async
{"merge_method": "squash", "merge_action": "direct_merge"}
-> {"status": "pending", "details": {"uuid": "...", "expected_head_sha": "A"}}
GET /repos/{owner}/{repo}/pulls/{n}/merge-async/{uuid}
-> {"status": "failed", "details": {"message": "6 of 6 required status checks are expected."}}
All six required checks had completed successfully on A — the very sha the endpoint echoed back as expected_head_sha. So the failure message is doubly misleading: the checks are neither missing nor pending on the head it claims to be merging.
Expected
When a stack rewrites a member branch, the pull request head follows the branch ref, exactly as an ordinary force-push does, and the merge box recovers.
Failing that, at least one route out of the state should stay open. update-branch and merge should not both refuse a stack whose only unmerged member targets the default branch directly — that combination leaves no supported way to fix a stale head, from the UI or the API.
Actual
The pull request was unmergeable from the UI and from every REST route, with no diagnostic anywhere — only an endless loading stack status spinner. Nothing in the UI indicated that the pull request was looking at a different commit than its own branch.
Workaround
Push any new commit to the branch. I merged the default branch into it with a plain local git merge + git push (no force). The synchronize event resynced the pull request head within seconds: mergeable flipped to true, mergeable_state went blocked -> unstable as the required checks re-ran on the new head, and the merge then completed normally.
One more UI symptom, after the merge
Clicking Merge on the recovered pull request also appeared to hang — the merge box kept spinning with no confirmation. The merge had in fact already succeeded: the API reported merged: true with a merge commit sha, and the commit was on the default branch. The merge box simply never updated.
Related but distinct
- #485 — a partial stack merge leaves a stale stack base sha, and Rebase stack replays already-merged commits. Here it is the pull request head that is stale, and no merge queue is involved.
- #450 — the stack view shows stale readiness while the API reports the pull requests clean. Here the API itself is the one reporting
UNKNOWN.
Both of those and this one share a theme: a stack that has had part of it merged keeps stale state somewhere, and the UI surfaces it as an unexplained spinner or an unexplained "not ready".
- Dominant language
- Go
- Stars
- 1.5k
- Forks
- 73
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 7
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from github/gh-stack
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
Difficulty 1/5 Under an hour Newbie friendliness 92/100
-
feature request topic: cli - general
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
feature request topic: auto-merge
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
bug topic: docs
Difficulty 1/5 Under an hour Newbie friendliness 68/100
Similar issues
-
feature-request helm
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
gravitational/teleport#69785 ·
-
bug
Difficulty 1/5 Under an hour Newbie friendliness 92/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
crossplane/crossplane#7859 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 90/100