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

未关闭
#519 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 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": true and still lists the merged pull request as a member.
  • Both stack members target the default branch directly (base.ref = default branch for each).

Reproduction

  1. Create a 2-entry stack on the default branch.
  2. Merge the bottom pull request. The stack stays open and keeps the merged pull request as position 1.
  3. Let the default branch advance by several commits over the following weeks.
  4. 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.)
  • statusCheckRollup reported SUCCESS — but for the stale head A.

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

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

github/gh-stack 的其他 Issue

查看 github/gh-stack 的全部 Issue

相似的 Issue

更多 Go Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。