Imported from wan-huiyan/agent-traffic-control (
plugins/agent-traffic-control/skills/gh-pr-merge-worktree-checkout-trap/SKILL.md). Install upstream withnpx skills add wan-huiyan/agent-traffic-control --skill gh-pr-merge-worktree-checkout-trap. Copyright stays with the author.
gh pr merge Worktree-on-Main Checkout Trap
Problem
gh pr merge --squash --delete-branch (and similar commands) fail with:
failed to run git: fatal: 'main' is already used by worktree at '/path/to/another/worktree'
even when the user has every right to merge the PR. The error makes it look like the merge failed, prompting the user to retry or panic.
The merge actually succeeded on GitHub. Only gh's post-merge local
side-effect — switching the local working tree to main so it's "ready"
after the merge — failed because git refuses to check out a branch that's
already checked out in another worktree.
Context / Trigger Conditions
You are in the right place if all of these are true:
- You ran
gh pr merge <number>(any merge mode:--squash/--merge/--rebase). - The exact error includes
fatal: 'main' is already used by worktree at. - The repo has multiple
git worktreeentries (rungit worktree listto confirm). - One of those worktrees is on
main(or whichever branch the PR merged into).
The command's first action is the GitHub merge API call; the local checkout is downstream of that. Failures in the local step do NOT roll back the merge.
Solution
Step 1: Verify the merge actually happened
gh pr view <number> --json state,mergedAt
# {"state":"MERGED","mergedAt":"YYYY-MM-DDTHH:MM:SSZ"}
If state is MERGED: the PR is merged. The error was a local cleanup
side-effect, not a merge failure. You can stop here. Optionally clean up
local artifacts:
# delete the remote branch (gh's --delete-branch flag also failed silently)
git push origin --delete <feature-branch-name>
# delete the local feature branch (it's already orphaned by the merge)
git branch -D <feature-branch-name>
# remove the worktree you used to develop the feature
git worktree remove .claude/worktrees/<your-feature-worktree>
# prune stale worktree refs
git worktree prune
If state is OPEN or CLOSED (without merge): something else went wrong.
The worktree error is masking a real failure. Re-run the merge with --admin
or check branch protection / required checks.
Step 1b (proactive alternative): Merge directly via GitHub API
If you already know gh pr merge will fail — because you're always in a
worktree where main is locked — skip gh pr merge entirely and call the
GitHub REST API directly. This avoids the error and the verify-after dance:
gh api repos/<owner>/<repo>/pulls/<number>/merge \
--method PUT \
--field merge_method=squash \
--field commit_title="your squash commit title" \
--field commit_message="optional body"
Expected response on success:
{"sha":"<merge-commit-sha>","merged":true,"message":"Pull Request successfully merged"}
merge_method accepts squash, merge, or rebase. The commit_title and
commit_message fields only apply to the squash method.
Note: this form does NOT delete the remote branch — add a separate
gh api -X DELETE repos/<owner>/<repo>/git/refs/heads/<branch> if needed,
or rely on the repo's auto-delete-head-branches setting.
Step 2 (optional, prevent next time): Use a non-conflicting worktree
If you frequently hit this, you have two options:
Option A — Don't keep a long-lived worktree on main. When you need to
sync local main, use a transient worktree (git worktree add /tmp/main main)
or just git fetch origin main from the feature worktree without checking it
out. This is the cleanest pattern.
Option B — Run gh from the worktree that already has main checked out.
That worktree's git is the one gh will succeed in checking out into.
Option C — Use gh api PUT directly (Step 1b above) instead of gh pr merge whenever you're in a long-lived worktree session. Two-liner, no
local checkout side-effect, no verify-after needed.
Most users won't bother with prevention — the post-failure gh pr view
verification is two seconds and the merge already worked.
Verification
gh pr view <number> --json state,mergedAt,mergeCommit
Expected after a "failed" merge that actually succeeded:
{
"state": "MERGED",
"mergedAt": "2026-04-27T15:04:53Z",
"mergeCommit": {"oid": "a4bd3004..."}
}
The presence of mergedAt and mergeCommit.oid confirms the merge landed.
Example
Symptom (real-world, S109a 2026-04-27):
$ gh pr merge 122 --squash --delete-branch
failed to run git: fatal: 'main' is already used by worktree at '/Users/<user>/Documents/the-project-repo/.claude/worktrees/compassionate-ishizaka-b7e3b7'
I had ~40 worktrees in .claude/worktrees/ from previous parallel sessions,
one of which was on main. gh tried git checkout main after the merge,
git refused.
$ gh pr view 122 --json state,mergedAt
{"mergedAt":"2026-04-27T15:04:53Z","state":"MERGED"}
Resolution: the merge had succeeded; the error was just gh's local cleanup choking. Same trap fired again 10 minutes later on PR #123 — same root cause (the same long-lived worktree was still on main).
Notes
- This is NOT specific to
--delete-branch. Any post-merge action that switches branches will hit it. The--delete-branchportion does seem to also fail silently when the local checkout fails, so manually delete the remote branch withgit push origin --delete <branch>orgh api -X DELETE repos/<owner>/<repo>/git/refs/heads/<branch>after. - WARNING — stacked-PR consequence of the API delete workaround: if the
branch you are deleting is the
baseof an open dependent stacked PR (PR2 was opened with--base <PR1-branch>instead of--base main), usinggh api -X DELETEon the ref will silently auto-close the dependent PR and the close is not reversible —gh pr reopenandgh pr edit --base mainboth fail because the base no longer exists. Seestacked-pr-base-branch-deletion-auto-closes-dependentfor the recovery (open a fresh PR) and the prevention pattern (retarget the dependent PR tomainBEFORE deleting the base branch, OR merge both PRs before any cleanup). - WARNING — never run the API ref-delete after a merge that ERRORED (S233b
2026-06-03): the
gh api -X DELETE …/refs/heads/<branch>cleanup above is ONLY safe once the merge is confirmed landed. If thegh pr mergereturned a transient GraphQL error — e.g. "Base branch was modified. Review and try the merge again." (common right after you push a follow-up commit and GitHub is mid-recompute) — the PR is still OPEN and UNMERGED. Deleting its head ref at that point auto-closes the PR unmerged (and the branch is gone, so a naivegh pr reopenfails until you re-push the branch). This is the same auto-close mechanism as the stacked-PR warning above, but applied to the PR's own head, not a dependent's base. Do not bundle the ref-delete in the same command block as the merge — that's how it runs unconditionally after a failed merge. Gate it:
Recovery if you already deleted the head of an unmerged PR:gh pr merge <n> --squash # NO --delete-branch (it half-runs on error) test "$(gh pr view <n> --json mergedAt --jq .mergedAt)" != "null" \ && gh api -X DELETE repos/<owner>/<repo>/git/refs/heads/<branch> # only if mergedgit push -u origin <branch>(re-creates the ref from your still-intact local commits) →gh pr reopen <n>→gh pr merge <n> --squash(no--delete-branch) → verifymergedAt→ THEN delete the ref. Nothing is lost as long as the local branch still holds the commits. - Same trap applies to
gh pr checkout <number>if the target branch is already checked out elsewhere. - Sequential-error variant (S8 2026-05-26, int_gtm_auditor): if you
recognise the trap and re-run from the main-repo worktree, you can hit a
SECOND error on the same merge:
The PR is already merged at this point (gh's "already merged" message precedes the error). The local cleanup is failing because the FEATURE worktree still has the feature branch checked out. Recovery order matters — clean up in this sequence from the main-repo worktree:failed to delete local branch <feature-branch>: failed to run git: error: cannot delete branch '<feature-branch>' used by worktree at '<feature-worktree>'
If you swap steps 3 and 4, the branch-delete fails for the same reason the second error fired. The first error ("'main' is already used by worktree") and this second error ("cannot delete branch ... used by worktree") are symmetric instances of the same one-checkout-per-branch invariant — first applied togit fetch origin git pull --ff-only # sync main locally git worktree remove ../<feature-worktree> # release the branch git branch -D <feature-branch> # now safe to deletemain, then applied to the feature branch. - The misleading part is that gh's exit code is non-zero, which makes scripts treat the merge as failed and may trigger retries or rollbacks that aren't needed.
git worktree listis the diagnostic — find the worktree onmainand decide whether to keep it.--auto --delete-branchenable-time variant (v1.3, S19 2026-05-27): the v1.0-1.2 variants all describe a POST-merge cleanup failure where the GitHub merge already succeeded. There is a sibling PRE-merge variant when checks are still pending:
This time the local$ gh pr merge 88 --auto --squash --delete-branch failed to run git: fatal: 'main' is already used by worktree at '...'--delete-branchcleanup attempt fires BEFORE the auto-merge intent is registered server-side.gh pr view N --json autoMergeRequestreturnsnull(no auto-merge enabled) AND the PR isn't merged. Workaround: re-run without--delete-branch:
Auto-merge enables; when checks pass the PR merges; then delete the worktree + branch manually:gh pr merge 88 --auto --squash # then verify gh pr view 88 --json autoMergeRequest,state
Diagnostic difference from v1.0-1.2: with the post-merge variant,git worktree remove .claude/worktrees/<feature-worktree> git branch -D <feature-branch> # may already be auto-deleted on # remote depending on repo settinggh pr view N --json statereturnsMERGED. With this enable-time variant,stateis stillOPENANDautoMergeRequestisnull. Always check BOTH fields before concluding the PR is done.
v1.5.0 variant — the REMOTE ref survives too, and stale SHAs keep resolving (2026-07-10)
When the post-merge local checkout fails, --delete-branch's remote deletion can also be
skipped — git ls-remote origin <branch> still shows the ref even though the PR reports MERGED.
Two downstream consequences observed:
- Docs cite SHAs that will die later. Pre-squash commits on the surviving remote branch
still resolve, so a handoff/PR-body written right after the merge can cite
<sha>"on main" when it is NOT an ancestor of main — it only resolves via the leftover branch and goes dead the moment someone prunes. Annotate such SHAs ("pre-squash branch commit, not on main") or cite the PR number instead. - Cleanup: delete the remote ref explicitly and verify:
gh api -X DELETE repos/<owner>/<repo>/git/refs/heads/<branch> git ls-remote origin <branch> | wc -l # expect 0
After ANY gh pr merge --delete-branch that printed the worktree error, check the remote ref —
don't assume only the local checkout failed.
References
git worktreedocs:git-worktree(1)— explains the one-checkout-per-branch invariant that produces this error.gh pr mergesource: github.com/cli/cli — the local checkout step is wrapped around the GitHub API merge call; failures in the wrapper do not roll back the API call.