# Claude Code worktrees isolate files, and nothing else

> A git worktree gives each Claude Code agent its own files and branch. It would have fixed the file-level half of my ten-worker fan-out, and none of the rest: the contract, the model budget, the stash and the ports stay shared.

Published 2026-11-11 by Nick Sawinyh.
Tags: ai, agents, engineering.
Canonical: https://sawinyh.com/blog/claude-code-worktrees-one-agent-per-checkout/

---

**TL;DR** Claude Code's worktree support gives a session its own checkout and branch, created under .claude/worktrees/ by claude --worktree or by a subagent with isolation: worktree. Anthropic's own docs describe the scope plainly: worktrees isolate file edits. Everything outside the checkout is still shared, including the git stash stack, the repository's .git directory, ports and dev servers, any node_modules you symlink, the API contract two agents build against, and the model budget. My ten-worker fan-out on 2026-08-31 split into two groups of failures. Peer files in git status, a rebuild wiping node_modules under a running dev server, and a per-directory framework lock are file-level collisions that a worktree per worker prevents. Contract drift and the shared model budget are not, and no worktree touches them. The rules that came out of it are one worker per worktree, freeze the shared contract before dispatching in parallel, and stage by path. The cost is real: every worktree needs its own install and build.

I dispatched ten workers in parallel on 2026-08-31 and the collisions were expensive. They fall into two groups.

The first group a worktree per worker would have prevented. Workers reported files in `git status` that were not theirs, one careless `git add -A` away from committing a peer's half-finished work. A composed-tree rebuild repeatedly wiped `node_modules` under a running dev server. Next.js's per-directory lock forced two workers to assemble throwaway trees just to run their gates. Those are what happens when workers step on the same files, and "one worker per worktree" is the rule that came out of them.

The second group a worktree cannot touch. A backend worker renamed an endpoint, and later removed a field from every message schema, each time after the frontend worker consuming it had reported green. And no model was set on any worker, so all of them drew on one budget until three hit a session limit.

So the title's claim holds, and I mean it as a limit: worktrees would have fixed the file-level half of that fan-out and none of the rest. This post covers both halves, plus the shared state that never caused an incident but that the docs and git confirm is there.

## What a Claude Code worktree actually is

A [git worktree](https://git-scm.com/docs/git-worktree) is a second working directory attached to the same repository. Per the git docs, it is linked to the current repository and shares everything except per-worktree files such as `HEAD` and the index.

Claude Code wraps that. Passing `--worktree` or `-w` with a name creates one and starts the session in it. [By default](https://code.claude.com/docs/en/worktrees) it lands under `.claude/worktrees/<name>/` at the repository root, on a new branch called `worktree-<name>`:

```bash
claude --worktree feature-auth
```

Leave the name off and Claude generates one. You can also tell a running session to work in a worktree and it uses the `EnterWorktree` tool. Subagents get the same treatment through frontmatter:

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

That example comes straight from the docs. A subagent with `isolation: worktree` gets a temporary worktree of its own, and the [subagent docs](https://code.claude.com/docs/en/sub-agents) say it is cleaned up automatically if the subagent makes no changes.

Some defaults matter before you rely on any of this. New worktrees branch from the repository's default branch on the remote, not from whatever you have checked out, unless you set `worktree.baseRef` to `"head"`. And a worktree starts clean: the docs say it is a fresh checkout, so you initialize the development environment there yourself.

## The docs already say the scope out loud

Anthropic's worktree page is more careful than most of what is written about it. A single sentence carries the whole argument: worktrees "isolate file edits." Subagents split work up inside a session, cross-session messaging passes findings between sessions, and the worktree is the file layer under both.

The page also lists what a worktree shares with the main checkout. The repository's `.git` directory, so commits from any worktree land in one repository. Project-scope plugins. "Don't ask again" permission approvals, which are saved to the main checkout's `.claude/settings.local.json` and apply in every other worktree of the repository. Untracked project skills, agents and commands, which load from the main checkout when the worktree has none of its own.

That is a list of shared state, written by the people who built the feature. It is a reasonable place to start asking what else is shared.

## What a worktree isolates, and what it does not

| Thing | Isolated per worktree? | What actually happens |
|---|---|---|
| Working files | Yes | Each worktree has its own copy on its own branch |
| `HEAD` and the index | Yes | Per-worktree per the git docs |
| The branch itself | Exclusive | `git worktree add` refuses a branch already checked out elsewhere unless forced |
| Commits and refs | No | All refs under `refs/` are shared, so every worktree writes into one history |
| The stash stack | No | Stash lives under `refs/`, so one stack serves every worktree |
| Dependencies and build output | Only if each tree installs its own | A fresh checkout has none; a symlinked `node_modules` is one directory seen by all |
| Ports and dev servers | No | Ports belong to the machine, not the checkout |
| The API contract between agents | No | Two worktrees can build against a spec that moves under one of them |
| Model budget and session limits | No | Every worker draws on the same account |
| Writes outside the worktree | Not enforced for shell | The docs say `cp` or a shell redirect into the main checkout is not refused by the isolation checks |

The last row surprised me. Claude Code does block edits that target the main checkout, blocks commands whose working directory resolves there, and blocks git redirected into it. The docs are explicit that none of those checks tracks which files a shell command writes, so a `cp` into the main checkout is treated like any other shell command and gated by your permission mode.

Each of the rows marked No is a way two agents can still hurt each other. Some of them cost me in the fan-out, covered further down. The stash, ports and symlinks never caused an incident for me, but the docs and git confirm them.

## The stash is a shared drawer

Nothing went wrong with the stash. I am including it because it is easy to assume a worktree separates it and it does not. The session that produced this post started in the shared main checkout with uncommitted files, and my SessionStart hook flagged that a branch flip there could silently revert another session's edits. The fix was a fresh worktree branched from `origin/main`.

The environment note for that worktree then warned that the git stash stack is shared with the main checkout and all other worktrees, and that a bare `git stash pop` could pop another session's changes.

That matches the git docs. All refs starting with `refs/` are shared, and a stash is stored as one. So the stack is a single drawer in a house with many rooms. An agent in worktree A runs `git stash`, an agent in worktree B runs `git stash pop`, and B has just applied A's half-finished work to its own branch.

The rule that follows is cheap. Set work aside with a temporary WIP commit on your own branch, not a stash. If a stash is unavoidable, tag it with a unique message, capture its SHA, apply by SHA, and drop it by tag. Bare `git stash pop` is the one command that assumes nobody else is in the house.

## Ports, node_modules and the cost nobody leads with

A worktree has its own files, so it has its own `node_modules` only if somebody installs one. This is where the pattern gets expensive, and the bill is honest to state: every worktree needs its own install and its own build. The docs say as much. A fresh checkout means initializing the environment in it, and `.worktreeinclude` will copy a gitignored `.env` across but cannot hand you a built project.

[@haipingfu](https://x.com/haipingfu/status/2082368222259736805) put the same cost well on 2026-07-29: the expensive part of a worktree is not cloning the source, it is rebuilding the world. Every worktree accumulates its own `node_modules`, `.next`, `target` and caches, and with agents "you end up with massive duplication." Their answer is a new snapshot-native workspace layer they were considering building. Mine is to pay the cost and keep the number of live worktrees small.

The tempting shortcut is to symlink `node_modules` from the main checkout into each worktree. It saves the install and it quietly removes the isolation. A symlinked directory is one directory. A reinstall or rebuild in any worktree rewrites it under all of them, and a dev server running in another tree is reading files that just changed shape.

The 2026-08-31 wipe was a different failure, and a worktree per worker would have removed it: a composed-tree rebuild repeatedly wiped `node_modules` under a running dev server, which means one directory with a writer and a reader. Separate trees, each with its own install, take that away. A symlink puts it back.

Ports follow the same logic. Dev servers in separate trees still want the same port, because ports belong to the machine and a worktree is a directory. [@itsmunter](https://x.com/itsmunter/status/2107441391060291908) joked on 2026-10-06 about the agent that realizes you have been hounding it because "you forgot it was in a worktree and the dev server points to main." The checkout moved and the running process did not. A port assignment per worker, written down before dispatch, costs nothing. Finding out that you have been testing the wrong tree costs an afternoon of confused debugging.

The same fan-out had a third file-level collision: Next.js's per-directory lock forced two workers to assemble throwaway trees just to run their gates. The tool locks per directory, so a directory per worker is what it needs. Giving each worker a worktree up front is the cheaper way to get what those workers built by hand.

## The contract is the thing two worktrees share by design

If two agents work on two halves of one change, they share an interface. A worktree cannot isolate that, because the interface is the point of the change.

On 2026-08-31 a backend worker renamed an endpoint, and later removed a field from every message schema. Each time it happened after the frontend worker consuming the contract had finished and reported green. "All gates pass" was true and stale at once. The frontend worker had verified against a contract that no longer existed.

File-level separation guarantees that nobody overwrites anybody. It guarantees nothing about whether the two halves still fit.

The fix is ordering, not more isolation. Either dispatch the halves sequentially, or have the half that owns the API publish the regenerated contract first and start the second half from it. Then, when the contract half lands, rerun the dependent half's gates. A green report from before the landing is a statement about the past.

This is the same split I drew when [agent teams](https://sawinyh.com/blog/claude-code-agent-teams-buy-disagreement/) came up: work divides cleanly when each worker owns different files, and falls apart when ownership overlaps. A worktree enforces file ownership. It does not enforce interface ownership, and interface ownership is the harder one.

## The budget is the least visible shared resource

I did not set a model on any of the ten workers. All of them inherited the most expensive one, about two million tokens burned, and three workers died at a session limit, two of them at the same moment because they drew on one budget.

Worktrees had nothing to do with it and could not have helped. Workers share an account however their files are laid out, and the account is what ran dry. The whole fan-out stalled.

If you want the longer argument about why splitting work is about context rather than speed, I made it in [the subagents post](https://sawinyh.com/blog/claude-code-subagents-context-not-speed/). The short version for this one: choose the model per worker at dispatch, by how much judgment the task carries. A worker following an already-written spec is mechanical work whatever the size of the diff.

## Staging is where a shared checkout turns into shared history

The quietest collision of the fan-out was in `git status`. Workers reported files that were not theirs. That can only happen when workers share a checkout, because separate worktrees each have their own status. A single careless `git add -A` would have committed a peer's half-finished work. This is the failure that "one worker per worktree" exists to fix, and it is the origin of that rule.

Claude Code does add fences here. While a session is isolated in a worktree, it blocks edits that target the main checkout, blocks commands whose working directory resolves there, and blocks git commands redirected into it. It also refuses a Bash command whose git it cannot prove stays inside the worktree.

I ran into that last one first-hand on 2026-10-08. A compound shell command was refused because the harness could not verify it stayed inside the worktree, and I was told to run plain, separate commands. It was mildly annoying and correct. The refusal is a [hook](https://sawinyh.com/blog/claude-code-hooks-enforce-not-advise/) in spirit, a deterministic check the model cannot argue with, and it fires on the class of mistake that costs the most.

Fences cannot stage for you, though. Staging by path is a habit worth writing into the dispatch brief: add the files you changed, by name, and nothing else, whenever another worker is live in the tree.

## Cleanup is a decision, so make it deliberately

What happens when the session ends is documented, and it is worth knowing before you rely on it. On exit Claude Code checks the worktree for changed or untracked files and new commits.

- A clean worktree from an unnamed session is removed along with its branch.
- A named session prompts you before removing a clean worktree.
- A worktree with work in it prompts you to keep or remove it. Removing deletes the directory and the branch with the work in them.
- A non-interactive `-p` run has no exit prompt, so its worktree is left alone, and so is the lock Claude Code took on it, until a later sweep or a manual `git worktree remove`.
- A subagent's worktree is removed on finish if the subagent made no changes. One with changes stays until a periodic sweep can remove it without losing work.

A stop hook that offers to tidy up fits here. Mine asks at session end, and I confirm the worktree's source tree matches `main` before removing it. After the pull request merged, that check is what made removal safe.

## What I do now

The practices that held up from that fan-out are small and mechanical, which is why they survived.

**One worker per worktree.** Never two agents in one checkout at once. When two workers need the same files, they run in sequence, not in parallel.

**Parallel only on independent surfaces.** Different files is necessary but not enough. Different ports, different install, different model budget.

**Freeze the shared contract before dispatching.** The half that owns the interface publishes the regenerated contract first, and the second half starts from it. Rerun the dependent half's gates after the contract half lands.

**Stage by path.** Never `git add -A` while another worker is live. Name the files.

**No bare stash.** Use a WIP commit. If you must stash, tag it and apply by SHA.

**Set the model on every worker at dispatch.** Inheriting the session model puts the workers and the orchestrator on one budget.

**Pay for the install.** A worktree you did not build is not a working checkout. Budget the install and build per tree, and keep the count of live trees small enough that you can.

This is the fourth layer of the harness I laid out in [the hub post](https://sawinyh.com/blog/claude-code-best-practices-harness-not-prompt/). Hooks enforce, skills teach, [memory](https://sawinyh.com/blog/claude-code-memory-what-not-to-save/) remembers, and worktrees isolate. Each fails in its own way, and this one fails when two agents share one checkout or a shared contract changes mid-flight. Both of those are failures of what the worktree leaves shared.

A worktree is a good tool and a narrow one. It keeps two agents from editing the same file. Keep the rest of the shared state on a list, because the first time it matters, nothing will have warned you.

## Frequently asked questions

### How do I run Claude Code in a git worktree?

Pass --worktree or -w with a name, for example claude --worktree feature-auth. By default Claude Code creates the worktree under .claude/worktrees/<name>/ at the repository root, on a new branch named worktree-<name>, and starts the session there. Leave the name out and Claude generates one. You can also ask a running session to work in a worktree, and a custom subagent can set isolation: worktree in its frontmatter to get a temporary one of its own.

### What happens to the worktree when the session exits?

Claude Code checks the worktree for changed or untracked files and new commits. A clean worktree from an unnamed session is removed along with its branch. A named session prompts you first, and a worktree with work in it prompts you to keep or remove it. Non-interactive runs with -p have no exit prompt, so their worktrees are left in place. Subagent worktrees are removed when the subagent finishes without changes, and a periodic sweep handles older ones.

### What does a git worktree not isolate?

It isolates the working files, HEAD and index. Per the git documentation, refs starting with refs/ are shared, and the stash is one of them, so the stash stack is common to every worktree. Ports, running dev servers, installed dependencies you share by symlink, an API contract two agents both depend on, and your model usage limits all sit outside the checkout and stay shared.

### Do I need a separate install and build in each worktree?

Yes. A worktree is a fresh checkout, and the Claude Code docs tell you to initialize the development environment inside it, by asking Claude to install dependencies or running your setup yourself. A .worktreeinclude file copies gitignored files such as .env into each new worktree, but it does not give you a built project. That install and build cost is the main price of the pattern.


---

Source: Nick Sawinyh, https://sawinyh.com/blog/claude-code-worktrees-one-agent-per-checkout/
Nick Sawinyh writes on AI, engineering, product, and crypto business development at sawinyh.com.
Site index for agents: https://sawinyh.com/llms.txt
