gh-pr-merge-worktree-checkout-trap · v1.3.0 · 2026-05-29 · sha256 4286e4063dded960

gh-pr-merge-worktree-checkout-trap v1.3.0A

Immutable. This exact content is served forever at /api/v1/blob/4286e4063dded960.

---
name: gh-pr-merge-worktree-checkout-trap
description: |
  Diagnose and bypass `gh pr merge --squash --delete-branch` failing with
  "failed to run git: fatal: 'main' is already used by worktree at ..." when
  another git worktree has main checked out. Use when: (1) you run gh pr merge
  and see this exact error, (2) you have multiple worktrees in the repo (e.g.
  `.claude/worktrees/*`), (3) the error appears even though the GitHub merge
  itself looks fine. The merge SUCCEEDED on GitHub — only gh's local-side
  effect (post-merge `git checkout main`) failed. Verify via
  `gh pr view N --json state,mergedAt`; if state=MERGED, you're done. This
  also applies to `gh pr checkout` and any other `gh` subcommand that tries
  to touch the local main branch while another worktree has it claimed.
  v1.2.0 (2026-05-26) adds the sequential-error variant: if you re-run from
  the main-repo worktree after the first error, you can then hit "cannot
  delete branch <feature-branch> used by worktree at <feature-worktree>" —
  same one-checkout-per-branch invariant, this time applied to the feature
  branch. Cleanup order: `git worktree remove` BEFORE `git branch -D`.
  v1.3.0 (2026-05-27) adds the `--auto --delete-branch` enable-time
  variant: when checks are still pending, `gh pr merge N --auto --squash
  --delete-branch` fails locally (same "main is already used by worktree"
  message) BEFORE the auto-merge intent is registered server-side —
  `gh pr view N --json autoMergeRequest` returns null. Workaround: drop
  `--delete-branch` (`gh pr merge N --auto --squash`), then `git worktree
  remove` + `git branch -D` manually after the merge lands. The post-merge
  variant in v1.0-1.2 succeeds the merge first then chokes on cleanup;
  this v1.3 variant chokes BEFORE the server-side enable, so verifying
  with `--json autoMergeRequest` (not just `state`) is required.
author: Claude Code
version: 1.3.0
date: 2026-05-27
---

# `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 worktree` entries (run `git worktree list` to 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

```bash
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:

```bash
# 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:

```bash
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:
```json
{"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

```bash
gh pr view <number> --json state,mergedAt,mergeCommit
```

Expected after a "failed" merge that actually succeeded:

```json
{
  "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-branch` portion does seem to
  also fail silently when the local checkout fails, so manually delete the
  remote branch with `git push origin --delete <branch>` or
  `gh 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 `base` of an open dependent stacked PR
  (PR2 was opened with `--base <PR1-branch>` instead of `--base main`),
  using `gh api -X DELETE` on the ref will silently auto-close the dependent
  PR and the close is **not reversible** — `gh pr reopen` and `gh pr edit
  --base main` both fail because the base no longer exists. See
  [`stacked-pr-base-branch-deletion-auto-closes-dependent`](../stacked-pr-base-branch-deletion-auto-closes-dependent/SKILL.md)
  for the recovery (open a fresh PR) and the prevention pattern (retarget
  the dependent PR to `main` BEFORE deleting the base branch, OR merge both
  PRs before any cleanup).
- 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:
  ```
  failed to delete local branch <feature-branch>: failed to run git: error:
  cannot delete branch '<feature-branch>' used by worktree at '<feature-worktree>'
  ```
  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:
  ```bash
  git 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 delete
  ```
  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 to `main`, 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 list` is the diagnostic — find the worktree on `main` and
  decide whether to keep it.
- **`--auto --delete-branch` enable-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:
  ```
  $ gh pr merge 88 --auto --squash --delete-branch
  failed to run git: fatal: 'main' is already used by worktree at '...'
  ```
  This time the local `--delete-branch` cleanup attempt fires BEFORE the
  auto-merge intent is registered server-side. `gh pr view N --json
  autoMergeRequest` returns `null` (no auto-merge enabled) AND the PR
  isn't merged. Workaround: re-run without `--delete-branch`:
  ```bash
  gh pr merge 88 --auto --squash
  # then verify
  gh pr view 88 --json autoMergeRequest,state
  ```
  Auto-merge enables; when checks pass the PR merges; then delete the
  worktree + branch manually:
  ```bash
  git worktree remove .claude/worktrees/<feature-worktree>
  git branch -D <feature-branch>   # may already be auto-deleted on
                                   # remote depending on repo setting
  ```
  Diagnostic difference from v1.0-1.2: with the post-merge variant,
  `gh pr view N --json state` returns `MERGED`. With this enable-time
  variant, `state` is still `OPEN` AND `autoMergeRequest` is `null`.
  Always check BOTH fields before concluding the PR is done.

## References

- `git worktree` docs: [`git-worktree(1)`](https://git-scm.com/docs/git-worktree) — explains the
  one-checkout-per-branch invariant that produces this error.
- `gh pr merge` source: [github.com/cli/cli](https://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.