Worktrees: The Missing Primitive for Multi-Agent Coding
At the start of a session this morning I checked something I’d never thought to check: which branch each of my repos was actually sitting on. 13 of 38 were on stale feature branches. Not one of them deliberately — that was just where the last agent to touch the repo had left it.
Nothing was broken. That’s what made it interesting. Every one of those repos would have happily accepted the next edit, on the wrong branch, without a word.
The mechanism is dull once you see it: one agent runs git switch while another has
uncommitted edits in the same checkout. Now the second agent’s work is on someone else’s
branch, or blocked by a dirty tree it didn’t create. With several agents live against the
same repos, that isn’t an edge case — it’s the default outcome, and the only variable is
how long before you notice.
I already had a workaround for it. My org-wide CLAUDE.md had grown a defensive rule:
always run git branch --show-current before you commit. Which tells you the wound was
known. A rule that says “check you’re not standing in a hole” is not a fix; it’s a
reminder to look down.
What a worktree actually is
A second working directory, with its own HEAD and its own index, sharing one object
store and one set of refs with the original checkout.
The important word is sharing. This is not a clone. There’s no second copy of history, no second remote, no re-fetch. A commit made in one worktree is instantly visible in the other — same objects, same refs, one repository wearing two hats. Creating one is near-instant and costs roughly what the checked-out files cost; throwing it away costs nothing.
That’s the whole feature. It’s the mental model people get wrong, not the mechanics.
The mental model
Three claims. Get these and everything else in this article is a consequence.
One repository, several working surfaces. Most of us carry a model where the repo and the directory are the same object. They aren’t, and worktrees are where that assumption visibly breaks. The repository is the object store and the refs; a working surface is one checkout of one commit. You can have several surfaces onto one repository, and only the surfaces are exclusive.
The branch is the lock. This is the part almost every explainer skips, and it’s the reason worktrees belong in a multi-agent setup at all: git refuses to check out the same branch in two worktrees. Not a warning — a refusal.
Think about what that converts. Without worktrees, two agents on one branch is a silent stomp: no error, no signal, just work quietly landing somewhere it shouldn’t. With worktrees, the same situation is a hard error at the moment of the attempt. You are not buying tidiness. You are buying a mutex you cannot forget to take, because taking it is the same action as starting work.
The failure mode doesn’t go away. It stops being silent, which is the only property that ever mattered.
I’ve now hit this refusal for real, which is why I trust it more than I did as a claim in the docs. The collision announced itself instead of corrupting something.
The boundary stops at git. A worktree isolates the working tree and HEAD. Nothing
else. Postgres, ports, Docker containers, .env files, installed dependencies,
self-hosted runners, deploy targets — all still shared, all still exactly as collidable as
they were yesterday. Two agents running alembic migrations against the same staging
database collide identically with or without worktrees.
I’ve put this in the mental model rather than filing it under gotchas, deliberately. A model that omits it doesn’t leave you neutral, it leaves you confident — and confident is worse than uninformed when the thing you’re confident about is isolation you don’t have.
How to apply it
Here’s the part that surprised me: the mechanical cost is close to zero. In Claude Code
it’s EnterWorktree at the start and ExitWorktree at the end. No configuration, no
setup ceremony, nothing to maintain.
Which means the adoption question isn’t “is this worth the effort” — it plainly is. The cost is entirely in four rules that sit around those two calls, and mostly in the third.
1. Gitignore the worktree directory before you create one
A Claude Code worktree lives inside the project, at .claude/worktrees/. If that path
isn’t ignored, git add -A stages the entire worktree as a gitlink — a
submodule-shaped entry pointing at a sha that exists on no remote. Clones then break for
everyone else.
$ git worktree add .claude/worktrees/agent-b -b agent-b$ git add -Awarning: adding embedded git repository: .claude/worktrees/agent-b$ git ls-files -s160000 f4015a6... 0 .claude/worktrees/agent-b ← not a fileThat 160000 is the mode bit for a gitlink. If you see it next to a path you thought was
source, this is what happened.
The same “it’s inside the project” property has a second consequence, and this one cost me more. Your linter and your test runner glob that directory too. In our core dashboard repo, a leftover worktree sitting at an older commit produced 98 lint errors and 4 test failures locally — every one of them naming the repo’s own files, none reproducible, while CI stayed green throughout, because CI clones fresh. The stale copy of the test suite was being collected and run, resolving its imports against the root checkout.
A tool reporting failures in files you didn’t touch, whose fix is not in those files, and which the pipeline can’t reproduce, costs far more time than the noise looks like it should.
The same property cuts the other way too, and it’s the pleasant surprise of the pair: I
built this cut of glitchedpixel.io inside a brand-new worktree without running npm install at all. Node
resolves node_modules upward, finds the parent checkout’s, and everything just works —
the worktree’s own node_modules ended up containing nothing but build caches. Convenient,
and worth registering as one more thing your worktree is sharing rather than isolating.
Your dependencies are not a copy.
2. Write tasks yes, read-only no
This is the rule I’d hand someone who wanted one line. If the task will edit, commit, or branch, take a worktree. If it’s investigation — reading code, tracing a call path, answering a question — don’t.
A worktree for read-only work buys you no isolation you needed and leaves behind setup, disk, and something to clean up. The lock is the value, and there’s nothing to lock.
3. Teardown is the actual failure mode
Note the asymmetry. Creation is a single call and everyone gets it right, every time.
Cleanup depends on someone answering a prompt at session exit — and ExitWorktree only
handles worktrees from the same session. A session that gets interrupted leaves its
worktree behind with nothing left in the world that knows to remove it.
Two things then outlive the work. The registration, which survives even after the
directory is deleted, and which git worktree prune exists specifically to clear. And the
branch, which nothing cleans up at all.
Neither is dangerous. Both are debris, and debris in a repo has a way of costing more to identify than to remove — an orphaned branch nobody recognises has to be proven disposable before anyone will delete it.
4. Learn to tell a live worktree from an abandoned one
git worktree list shows you registrations. It does not tell you whether anything is
still using them — which is the question you actually have.
Add --porcelain and you get the lock reason, including the session pid:
$ git worktree list --porcelainworktree /workspace/my-repo/.claude/worktrees/fix-login-redirectHEAD ae64e085740057d2118939628b614498ae985227branch refs/heads/worktree-fix-login-redirectlocked claude session fix-login-redirect (pid 462278 start 88257009)Then check whether that pid is alive. If it is, that’s an agent mid-task and you leave it alone. If it isn’t, you’ve found an orphan. I ran exactly this check while writing this article, found two locked worktrees across the fleet, and both turned out to be live sessions doing real work in other repos. Without the pid I’d have been guessing, and the pessimistic guess would have been wrong.
One more trap in the same family: if your workspace root isn’t itself a git repo, a
worktree can end up registered to one repository while physically sitting under a
sibling’s .claude/worktrees/ directory. I’ve had this happen, and it’s genuinely
disorienting — no amount of searching the filesystem under the “right” repo will ever
find them. Only a per-repo git worktree list will.
What it actually bought me
Five things, in descending order of how much I care.
It changed what I’m willing to do. This is the real return, and it isn’t a
tidiness argument. I now run agents concurrently across control-api, firmware and
imp-player-ui without the background worry that one will quietly land work in another’s
tree. That confidence is new, and it’s what turned worktrees from a nice-to-have into the
default for write tasks.
The stomp became a hard error, and I’ve seen it fire. Not a docs claim any more.
The drift cleared. Those 13 stale branches are back to 0 of 38. Honest caveat: I measured that the same day as the rollout, and clearing drift is exactly what the rollout itself did. Whether it stays at zero is a claim this article can’t make yet — ask me in a month.
The workaround got deleted. The defensive git branch --show-current rule is
redundant now. Removing a rule is a better outcome than writing a clearer one.
And it cost almost nothing to adopt. Two tool calls.
The honest open question isn’t whether to use worktrees. It’s whether you’ll clean them up — because that’s the only part still left to a human remembering.
The commands worth knowing
git worktree add ../feature-x -b feature-x # creategit worktree list # per-repo — the ONLY way to find straysgit worktree list --porcelain # + lock reasons and session pidsgit worktree prune # clear registrations whose dirs are gonegit worktree remove <path> # proper teardownIn Claude Code: EnterWorktree and ExitWorktree. Worth knowing that EnterWorktree
only fires when you or a CLAUDE.md asks for it — there’s no settings flag that makes it
automatic — and ExitWorktree only cleans up worktrees from the session that made them.
Both of those facts point the same way.
Worktrees get sold as a convenience: work on two branches without stashing. That’s true, and it’s the least interesting thing about them.
Point several agents at one set of repos and they become a concurrency primitive — and
concurrency primitives are never judged on how easy they are to acquire. EnterWorktree
is one call. Setup has exactly one prerequisite, and you do it once per repo. Everything
that recurs is a release problem.
The half that’s automated is the easy half.