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
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 48/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- git, github, go, graphql
调研方向
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.
由索引模型根据 Issue 内容生成。
描述
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".
- 主要语言
- Go
- 星标
- 1.5k
- 派生
- 73
- 平均合并
- 1 天 8 小时
- 30 天内合并 PR
- 7
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
github/gh-stack 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 85/100
-
难度 1/5 1 小时以内 新手友好度 92/100
-
feature request topic: cli - general
难度 2/5 1-3 小时 新手友好度 68/100
-
feature request topic: auto-merge
难度 2/5 1-3 小时 新手友好度 68/100
-
bug topic: docs
难度 1/5 1 小时以内 新手友好度 68/100
相似的 Issue
-
feature-request helm
难度 2/5 1-3 小时 新手友好度 68/100
gravitational/teleport#69785 ·
-
bug
难度 1/5 1 小时以内 新手友好度 92/100
-
难度 2/5 1-3 小时 新手友好度 84/100
-
难度 2/5 1-3 小时 新手友好度 88/100
crossplane/crossplane#7859 ·
-
难度 1/5 1 小时以内 新手友好度 90/100