name: gh-pr-merge-unstable-state-needs-auto-and-watch-branch-deletes
description: |
Diagnose and recover from two adjacent gh pr merge failure modes that masquerade as merge
conflicts. Use when: (1) gh pr merge --squash (or --merge) errors with "To have the pull request
merged after all the requirements have been met, add the --auto flag" AND "Run the following to
resolve the merge conflicts locally" even though you just pushed a clean resolution; (2)
gh pr view <N> --json mergeable,mergeStateStatus returns MERGEABLE + UNSTABLE rather than
MERGEABLE + CLEAN; (3) you delete the remote branch (git push origin --delete <branch>)
immediately after gh pr merge returns a conflict warning thinking the merge succeeded — and
discover the PR has flipped to CLOSED rather than MERGED. The actual root cause for (1)/(2) is
almost always pending CI / branch-protection checks, NOT real merge conflicts; the fix is --auto
flag so gh queues the merge for when checks pass. For (3) — recovery requires restoring the remote
branch (git push -u origin <branch> from your local copy) and gh pr reopen <N> before retrying.
Sibling to gh-pr-merge-worktree-checkout-trap (different failure mode — worktree holding main).
author: Claude Code
version: 1.0.0
date: 2026-05-19
gh pr merge UNSTABLE state + branch-delete recovery
Problem
Two adjacent failure modes that read as "merge conflicts" but aren't:
Failure A: gh pr merge rejects with "conflicts" when there are none
You push a clean resolution of merge conflicts, GitHub shows the PR as MERGEABLE, and yet:
$ gh pr merge 915 --squash
To have the pull request merged after all the requirements have been met,
add the `--auto` flag.
Run the following to resolve the merge conflicts locally:
gh pr checkout 915 && git fetch origin main && git merge origin/main
The first line is the actual hint; the second is misleading boilerplate. The real cause is
mergeStateStatus: UNSTABLE — branch protection or CI checks are pending. gh can't merge yet
because the repo's rules aren't satisfied, not because of file-level conflicts.
Failure B: PR auto-closes if you delete the remote branch right after Failure A
Common pattern: previous PR on the same branch merged cleanly with gh pr merge <N> --squash. You
then do git push origin --delete <branch> to clean up. Habit kicks in for the next PR — you run
gh pr merge (gets Failure A above), notice the "conflicts" warning, run
git push origin --delete <branch> anyway thinking the merge already landed remotely...
$ gh pr view 915 --json state
{"state":"CLOSED"} # ← not MERGED — the PR closed because the branch was deleted with the merge un-queued
GitHub treats a deleted-branch PR with no merge in progress as abandoned and closes it.
Context / Trigger conditions
gh pr merge --squash(or--merge) returns with the "add the--autoflag" / "resolve conflicts" stderrgh pr view <N> --json state,mergeable,mergeStateStatusshows{state: OPEN, mergeable: MERGEABLE, mergeStateStatus: UNSTABLE}— theUNSTABLEis diagnostic; it means "no real conflicts, but checks aren't passing yet"- Or: a PR you just tried to merge is now
state: CLOSEDrather thanstate: MERGED, AND the remote branch is missing - Repo has branch protection rules (required status checks, required reviews) OR CI workflows that haven't completed yet
- You're in a multi-PR session where the previous PR merged cleanly, conditioning you to expect the same flow
Root cause
For Failure A: gh pr merge (without --auto) requires the PR to be fully ready to merge RIGHT
NOW. If branch-protection rules or CI checks are pending, gh reports it as a generic merge problem
with misleading conflict-resolution advice. The actual diagnostic is the
mergeStateStatus field — UNSTABLE means "pending checks", BLOCKED means "failed checks or
missing reviews", CLEAN means "ready to merge", DIRTY means "real file conflicts".
For Failure B: GitHub's UI treats "branch deleted + no merge in progress" as PR abandonment. The PR closes; the merge never happens; recovery requires restoring the branch AND reopening the PR.
Solution
Diagnose first — three-line state check
Before retrying or panicking:
gh pr view <N> --json state,mergeable,mergeStateStatus,statusCheckRollup
Interpretation:
state |
mergeable |
mergeStateStatus |
Meaning | Action |
|---|---|---|---|---|
| OPEN | MERGEABLE | CLEAN | Ready to merge | gh pr merge <N> --squash |
| OPEN | MERGEABLE | UNSTABLE | Pending CI / checks | gh pr merge <N> --squash --auto |
| OPEN | MERGEABLE | BLOCKED | Failed checks or missing required reviews | Fix the failed check or request review |
| OPEN | CONFLICTING | DIRTY | Real file conflicts | Resolve locally, push, retry |
| CLOSED | (any) | (any) | PR closed (often: branch deleted) | Restore branch + reopen (see recovery below) |
| MERGED | (any) | (any) | Already done | Nothing to do |
Fix Failure A — use --auto
gh pr merge <N> --squash --auto
# Returns silently; merge queues for when checks pass; GitHub auto-deletes remote branch on merge
--auto tells GitHub to wait for branch-protection requirements and then merge automatically. No
polling required — GitHub handles it. The remote branch is deleted by GitHub at merge time (no need
to do it yourself).
Fix Failure B — restore branch + reopen PR + retry
# Your local branch still has the commits — push it back up
git push -u origin <branch-name>
# Reopen the PR (gh closed it when the branch went away)
gh pr reopen <N>
# Verify it's back
gh pr view <N> --json state,mergeable,mergeStateStatus
# Then merge with --auto
gh pr merge <N> --squash --auto
If you've already discarded the local branch (e.g., git branch -D after the wrong-time delete),
recovery is harder — you may need to recover commits via git reflog or recreate from a colleague's
clone.
Verification
After applying --auto:
gh pr view <N> --json stateshould still showOPENimmediately after the command- Within minutes (or however long your CI takes), GitHub will merge automatically
- Final state:
gh pr view <N> --json state,mergedAtshows{state: MERGED, mergedAt: <timestamp>} - Remote branch is auto-deleted by GitHub
- You receive no email failure notification (a clean signal)
Example
Session ending PR #915 (a docs handoff), 2026-05-19:
$ gh pr merge 915 --squash
To have the pull request merged after all the requirements have been met, add the `--auto` flag.
Run the following to resolve the merge conflicts locally:
gh pr checkout 915 && git fetch origin main && git merge origin/main
$ gh pr view 915 --json state,mergeable,mergeStateStatus
{"mergeable":"MERGEABLE","mergeStateStatus":"UNSTABLE","state":"OPEN"}
^^^^^^^^
not DIRTY → no real conflicts; pending checks
$ gh pr merge 915 --squash --auto
# (silent success — queues for CI)
(Earlier in the same session, the same orchestrator hit Failure B on the same PR by running
git push origin --delete docs/s207-handoff immediately after gh pr merge returned the conflict
warning — the PR flipped to CLOSED. Recovered with git push -u origin docs/s207-handoff +
gh pr reopen 915 + gh pr merge 915 --squash --auto.)
Notes
- Don't reflexively delete the remote branch after
gh pr mergeerrors. The merge may not have landed. Checkgh pr view <N> --json state,mergedAtfirst; only delete ifmergedAtis non-null. --autois safe to use even when checks pass immediately. If the PR is already CLEAN,--automerges instantly. No downside to using it as the default.- Branch-protection diagnostics: if
mergeStateStatusstaysUNSTABLEfor >5min after a push, checkgh pr checks <N>to see which check is hanging. A common cause is apaths-filter workflow that didn't trigger because the PR touched no matching paths — that check stays "pending" forever and blocks merge until the rule is updated. - Sibling failure mode:
gh-pr-merge-worktree-checkout-trap— whengh pr merge --delete-branchfails withfatal: 'main' is already used by worktree at .... That's a LOCAL-side failure of the post-merge branch checkout; the GitHub merge itself succeeds. Different beast from the failures documented here — verify by checkinggh pr view <N> --json mergedAt. - Required-status-check repos: in repos with branch protection requiring specific status checks,
--autois essentially mandatory — no human can merge instantly if checks aren't done. Build the habit project-wide.
References
- gh pr merge — official docs
- GitHub mergeStateStatus enum reference
- Sibling skill:
~/.claude/skills/gh-pr-merge-worktree-checkout-trap/SKILL.md