Running Claude Code with worktrees (without losing your mind)
A field guide to the three mains, why git refuses to check out main twice, and the five traps that cost me real time once several agents were sharing one repository.
Why does git refuse to check out main twice?
-
01 of 03
One branch, one place
A branch can only be checked out in one worktree at a time. The refusal is git protecting you, not a bug.
-
02 of 03
Three mains
The remote branch, the local branch in the main checkout, and the folder a deploy pulls into. Most trouble is confusing two of them.
-
03 of 03
Bring main to you
Work in a worktree, pull the remote to you, and deploy by pulling main in its own checkout. The five traps below are what happens otherwise.
If you run several Claude Code sessions at once, each one spins up its own git worktree: a separate folder sharing one repo. It’s a great workflow, but it trips people up. Here’s the mental model and the handful of habits that keep it clean.
The one rule that explains everything
A branch can only be checked out in one place at a time.
That’s why, when an agent tries git checkout main, it says “main is already checked out in another worktree.” That’s not a bug. It’s git protecting you. Which leads to the real lesson.
There are three “mains”. Know which is which
| Ref | What it is | Stays current via |
|---|---|---|
origin/main | The truth: latest merged code on the remote | git fetch (always) |
local main | One checkout on your machine = what you deploy to prod | a git pull when you deploy |
| your worktrees | Your active work, each on its own branch | you |
The trap: people tell their agent “get on main so you have the latest.” But local main is often the stalest thing you own. It only moves when you pull. The freshest ref is always origin/main.
Habit 1: Never work on main. Work in worktrees.
Keep local main clean; never commit to it directly. Do all work on branches in worktrees. This sounds restrictive. It’s actually what makes everything else bulletproof (see Habit 3).
Habit 2: Bring origin/main to you. Don’t go to main.
You never need to be on main. Just pull its commits into your branch:
git fetch origin # cheap, instant, never locked — do this anytime
git rebase origin/main # replay your work on top of the latest
Start every new session with this. One git fetch catches up on all PRs merged since. You don’t sync per-merge. Verify you’re current:
git rev-list --left-right --count origin/main...HEAD # "0 N" = up to date
Habit 3: Deploy by pulling main, then rebuilding from it
Deployment and git are separate ideas. “main” is a git label; “deployed” is just which folder you last built from. Pick one checkout as your deploy source (your local main) and ship from it:
cd /path/to/main-checkout
git pull --ff-only origin main # fast-forward main to latest, then...
<your build/restart command> # ...rebuild from this now-current folder
That git pull is the only thing that should ever move local main, and because you never commit to it (Habit 1), the pull can only fast-forward: no conflicts, ever. --ff-only is the safety latch that aborts instead of making a mess if anything’s off.
The payoff: local main is automatically “current as of your last deploy,” and prod always ships the latest merged code, without you ever hand-managing it.
The cheat sheet
git worktree list # which folder holds [main]? which holds your work?
git fetch origin # refresh the truth — do this constantly
git rebase origin/main # pull latest into the branch you're on
# deploy:
cd <main-checkout> && git pull --ff-only origin main && <build>
TL;DR: Work in worktrees, never on main. git fetch is your “stay current.” origin/main is truth; local main is just your deploy button. Pull it only when you ship.
Five traps, added the hard way
The habits above are the mental model. These are the specific things that have cost me hours since, all of them consequences of the same fact, which is that a worktree shares a repository but not an environment.
1. Your tools may be pointing at the wrong tree. This is the expensive one. An agent handed an absolute path (from a note, a config file, a previous session) will happily read and edit the main checkout while believing it’s working in your worktree. Everything succeeds. Nothing you did is in your branch, and if that main checkout is what you deploy from, you’ve edited production’s source directly. Verify the path before the first edit, not after the diff looks wrong.
2. node_modules is not shared, and a fresh worktree may not install cleanly. Each worktree needs its own install, and a plain npm ci can fail on peer-dependency resolution in a monorepo that the lockfile in the main checkout papered over. If you have a shared internal package that others build against, build it first; otherwise every dependent fails in a way that looks like a code error.
3. npx <tool> downloads the latest, not your pinned version. Run a formatter or linter through npx in a worktree where dependencies aren’t installed, and you get whatever version was published this week, with different defaults. Mine silently reformatted a thousand lines of unrelated documentation into a diff I nearly committed. Check for the local binary in node_modules/.bin/ first, and glance at git diff --stat before every commit.
4. Disk is the real constraint on parallelism. N worktrees means N sets of dependencies, N build caches, and, if you’re running container builds, N image layers. The failure mode isn’t slowness, it’s an out-of-space error partway through a build, which leaves things in a state that is genuinely annoying to unpick. Check headroom before you fan out, and clean up your own dependencies when a worktree is done.
5. The stash stack is shared. All worktrees on one repository share a single stash stack. If two sessions stash at once, git stash pop can restore someone else’s work into your tree. Prefer a throwaway commit for setting work aside. If you must stash, tag it (git stash push -m "<something-unique>") and restore by looking it up, never by popping blind.
The pattern behind all five: a worktree isolates your branch, not your machine. Anything the filesystem shares (dependencies, disk, the stash, absolute paths) is still shared, and that’s precisely where the surprises live.