Git worktrees for parallel coding agents: what they isolate and what they share
7m read time

Git worktrees for parallel coding agents: what they isolate and what they share

Git worktrees give parallel coding agents their own files and nothing else. What --worktree isolates in Claude Code, what stays shared across the one .git, and the isolation choices the flag does not make for you.

Open three terminals, run claude --worktree in each, and ten seconds later you have three agents working on three features in three directories. Nothing overlaps. Nothing collides.

That is the pitch, and the file-level half of it is true.

The rest of the machine is shared, and that is the half with teeth.

What a git worktree actually isolates

A git worktree is a second working directory with its own checked-out files, its own index and its own branch, pointing at the same repository. One clone, several trees. That is the whole idea.

Claude Code has first-class support for them. Pass --worktree (or -w) with a name and you get a directory at .claude/worktrees/<name>/ on a new branch called worktree-<name>:

bash
claude --worktree feature-auth

Leave the name off and it invents one, something like bright-running-fox. Add --tmux and the session lands in its own tmux window. Hand it a pull request instead of a name and it fetches pull/<number>/head for you:

bash
claude --worktree "#1234"

Quote it, or your shell treats everything after the # as a comment.

Subagents get the same treatment. Add isolation: worktree to a custom subagent's frontmatter and every run happens in a throwaway checkout:

markdown
---
name: refactorer
description: Applies mechanical refactors across many files
isolation: worktree
---

While that agent runs, Claude Code calls git worktree lock on its directory so the periodic cleanup sweep cannot pull the floor out from under it.

Put .claude/worktrees/ in your .gitignore before you start, or your main checkout fills up with untracked files belonging to other agents.

The base branch is not where you are standing

Here is the first thing that catches people out. New worktrees do not branch from your current HEAD. They branch from your repository's default branch on the remote.

That is the worktree.baseRef setting, and it defaults to "fresh". So you are four commits deep on a feature branch, you spin up a worktree to have an agent fix something adjacent, and the agent quietly starts from origin/main. Everything you have not pushed is absent.

For a genuinely independent task that is the right default. For the common case, where the parallel work builds on what you are already doing, say so:

json
{
  "worktree": {
    "baseRef": "head"
  }
}

Inside a worktree, "head" resolves to that worktree's HEAD rather than the main checkout's.

A fresh checkout is not a working environment

A worktree checks out tracked files. Only tracked files.

So your .env is not there. Your .env.local is not there. Your node_modules is not there.

The env half has a first-party fix: a .worktreeinclude file in the project root, gitignore syntax. Anything matching a pattern that is also gitignored gets copied into every worktree Claude creates.

text
.env
.env.local
config/secrets.json

Dependencies are your problem. Every worktree needs its own install before an agent can build or test anything, and people have watched auto-created worktrees eat ten gigabytes in an afternoon.

On npm that is the price of admission. pnpm's content-addressable store symlinks packages from one place on disk, so the second install costs almost nothing. This is the rare situation where the package manager argument stops being religious and starts being arithmetic.

Ports and databases were never isolated

Worktrees give each agent its own files. Everything those files talk to is still shared.

Two agents running npm run dev will fight over port 3000. They also share your local Postgres, your Docker daemon, your Redis, your build caches and every artefact directory outside the repository. Agent A runs a migration, agent B's tests start failing, and neither has any way to work out why.

Nothing in the worktree machinery helps here, because git has no opinion about your dev server. Assign a port per worktree in that worktree's .env.local, give each one its own database name, or accept that one agent at a time gets to run the stack.

Real runtime isolation needs containers. Worktrees are the git layer. A container is the process layer. They compose, and neither one stands in for the other.

The one .git they all write to

Every worktree shares the repository's .git directory. That sharing is the design, and it is what makes worktrees cheap enough to create per task.

It also makes refs global.

  • Stashes are shared. The stash list lives in .git. An agent that stashes in one worktree has put that stash in every session's list, where it can be popped in the wrong tree.
  • Branches are shared. Delete a branch in one worktree and it is gone everywhere. You cannot check the same branch out twice, which is the one collision git actually catches for you.
  • Force operations are shared. A reset --hard or a rebase on a branch another worktree is standing on does exactly as much damage as it would without worktrees.

Read that list back and you will notice it is the destructive half of git. Worktree isolation protects your files from a confused agent and does nothing about the commands that rewrite history. If you would not hand an agent git push --force in your main checkout, your permission rules still have to say so inside a worktree.

The isolation only runs one way

That permission point has a sharp edge worth knowing about.

Approve a Bash command with "Yes, don't ask again" inside a worktree session and the rule is written to the main checkout's .claude/settings.local.json. It applies in your main checkout, in every other worktree, and it outlives the worktree that produced it.

That is the right behaviour. Nobody wants to re-approve npm test in five throwaway directories. Be clear about the shape of it though: files flow inward and stay put, permission grants flow outward and stick.

The disposable environment writes to your durable configuration. An answer you give at two in the morning in a directory you are about to delete is a permanent one.

Cleanup needs a habit

When you exit an interactive worktree session, Claude checks for changed files, untracked files and new commits. Clean and unnamed, it removes the directory and the branch. Anything else, it asks whether to keep or remove.

Two ways that stops happening:

  • -p runs have no exit prompt, so headless sessions never clean up after themselves. Clear them with git worktree remove.
  • Subagent worktrees holding work stay on disk until a periodic sweep can remove them without losing anything, governed by your cleanupPeriodDays setting.

Run git worktree list now and then. If you script agents at all, the disk fills up long before git starts complaining.

Generation parallelises, review does not

Everything above is solvable with a morning of setup. The real ceiling sits somewhere else.

People running this seriously settle at three to five concurrent agents. The tooling copes fine at eight, and the ceiling is you. Five worktrees produce five branches that all need reading, and reading is the part that never got faster.

Teams that go higher get there by removing overlap rather than adding isolation: one file, one owner, no two running tasks touching the same code. That rules out conflicts by design instead of resolving them afterwards, and it forces the planning to happen before any agent starts, which is where it belonged anyway.

Speed got cheap and judgement didn't, and parallel agents are the purest form of that trade. You can multiply generation with a shell flag. Your capacity to understand what comes back is exactly what it was yesterday.

One task, one worktree, closed when done

That is the same rule as clearing a session on a task boundary, applied to the filesystem instead of the context window. A long session accumulates stale context. A long-lived worktree accumulates a stale environment: a branch that drifted from main last Tuesday, dependencies from two lockfiles ago, a database left mid-migration.

Isolation is a set of decisions about files, refs, ports, data and permissions, and --worktree makes exactly one of them for you.

Make the other four deliberately.

(26 of 26)