Merge queue didnt close jeduden/mdsmith#154
Merge commit 1d6e852 ("Merge PR #154") committed at 08:53:02Z — that's what landed on main via the batch.
PR head 8540c5a committed at 08:53:54Z — a new commit was pushed to the PR branch 52 seconds after the batch snapshot was taken, while the batch's CI was still running.
So the sequence was:
08:53:01Z queue picks up PR #154. Snapshots head SHA 150c2a4.
08:53:02Z repos.merges API creates merge commit 1d6e852 on the batch branch (parent contains 150c2a4).
08:53:54Z a new commit (8540c5a) is pushed to claude/plan-50-..., moving PR #154's head forward. The batch is oblivious.
08:55:52Z action fast-forwards main to the batch head. 150c2a4 is now on main, but the PR's current head 8540c5a is not.
GitHub's "auto-close when PR head is reachable from base" heuristic fires on the PR head at fast-forward time — 8540c5a, not 150c2a4 — and finds it unreachable. PR stays open.
For #158 nobody pushed during the batch, so its head was still reachable → auto-closed fine.
The action's commentMerged posted on both PRs anyway because it only checks its own result.merged list — it never verifies that GitHub actually closed the PR.
Root cause in jeduden/merge-queue-action
src/gitops.ts#fastForwardMain + the post-FF loop in src/action.ts rely entirely on GitHub's auto-close. There's no protection against the PR head moving between snapshot and fast-forward. Any push to a queued branch during batch CI (very likely here — an agent was still pushing fixup commits for Copilot's reviews on this PR) loses auto-close.
Recommended fix
Two changes in jeduden/merge-queue-action:
Detect head drift before fast-forward (src/action.ts, just before fastForwardMain): for each PR in the batch, re-fetch pulls.get and compare head.sha to the snapshot used for mergeBranch. If any drifted, skip the fast-forward and re-batch — the CI result is no longer valid for the new head anyway.
Belt-and-suspenders in the post-FF loop: after fast-forward, for each merged PR, call pulls.get and if state === "open" after a brief retry, explicitly close it via pulls.update({ state: "closed" }) and leave a comment linking to the merge SHA. This catches the current case where the drift wasn't detected.
Merge queue didnt close jeduden/mdsmith#154
Merge commit 1d6e852 ("Merge PR #154") committed at 08:53:02Z — that's what landed on main via the batch.
PR head 8540c5a committed at 08:53:54Z — a new commit was pushed to the PR branch 52 seconds after the batch snapshot was taken, while the batch's CI was still running.
So the sequence was:
08:53:01Z queue picks up PR #154. Snapshots head SHA 150c2a4.
08:53:02Z repos.merges API creates merge commit 1d6e852 on the batch branch (parent contains 150c2a4).
08:53:54Z a new commit (8540c5a) is pushed to claude/plan-50-..., moving PR #154's head forward. The batch is oblivious.
08:55:52Z action fast-forwards main to the batch head. 150c2a4 is now on main, but the PR's current head 8540c5a is not.
GitHub's "auto-close when PR head is reachable from base" heuristic fires on the PR head at fast-forward time — 8540c5a, not 150c2a4 — and finds it unreachable. PR stays open.
For #158 nobody pushed during the batch, so its head was still reachable → auto-closed fine.
The action's commentMerged posted on both PRs anyway because it only checks its own result.merged list — it never verifies that GitHub actually closed the PR.
Root cause in jeduden/merge-queue-action
src/gitops.ts#fastForwardMain + the post-FF loop in src/action.ts rely entirely on GitHub's auto-close. There's no protection against the PR head moving between snapshot and fast-forward. Any push to a queued branch during batch CI (very likely here — an agent was still pushing fixup commits for Copilot's reviews on this PR) loses auto-close.
Recommended fix
Two changes in jeduden/merge-queue-action:
Detect head drift before fast-forward (src/action.ts, just before fastForwardMain): for each PR in the batch, re-fetch pulls.get and compare head.sha to the snapshot used for mergeBranch. If any drifted, skip the fast-forward and re-batch — the CI result is no longer valid for the new head anyway.
Belt-and-suspenders in the post-FF loop: after fast-forward, for each merged PR, call pulls.get and if state === "open" after a brief retry, explicitly close it via pulls.update({ state: "closed" }) and leave a comment linking to the merge SHA. This catches the current case where the drift wasn't detected.