CLClaude Lessons Try the free lessons

Claude Lessons / Guides

Claude Code Worktrees: Run Parallel Sessions Without Conflicts

The moment you run a second Claude Code session on the same repository, you have a problem: two agents editing the same files, two sets of uncommitted changes, one branch. Git worktrees are the fix — each session gets its own checkout and its own branch, sharing one repository history. Claude Code has a --worktree flag that creates one for you, enforces the isolation at the tool level so a session physically cannot write back into your main checkout, and cleans up afterward. Most guides on this topic stop at git worktree add, which is the manual way and misses everything Claude Code adds on top. This one covers the flag, the settings, subagent isolation, the cleanup rules, the errors you will actually hit — and the decision most people get wrong, which is whether they wanted worktrees at all instead of subagents or agent teams.

Illustration of two separate but connected workbenches, representing parallel Claude Code worktrees

What a worktree is, and the problem it solves

A git worktree is a second working directory with its own files and its own checked-out branch, backed by the same repository history, objects, and remotes as your main checkout. It is not a clone. There is one .git directory; the worktree just points at it.

That distinction is the whole value. A clone would give you isolation but a separate history, so you'd be pushing and pulling to move work between them. A worktree gives you isolation and a shared history, so a branch created in one worktree is immediately visible from the other, and git log, git commit, and git push all behave normally from inside it.

The Claude Code problem this solves is specific. One session refactoring the auth module and another fixing a bug in the same repository will step on each other in three ways: they overwrite each other's edits, they see each other's half-finished work as if it were the codebase, and they fight over which branch is checked out. Put each session in its own worktree and all three disappear. The sessions don't know the other exists.

Worktrees need git. For SVN, Perforce, Mercurial, or anything else, you replace the creation and cleanup logic with WorktreeCreate and WorktreeRemove hooks. That's covered in troubleshooting, since it changes several behaviors.

The 60-second version

Pass --worktree (or -w) with a name:

claude --worktree feature-auth

That creates a worktree at .claude/worktrees/feature-auth/ at your repository root, on a new branch named worktree-feature-auth, and starts Claude inside it. Run the same command with a different name in a second terminal and you have two isolated sessions. Omit the name entirely and Claude Code generates one for you, like bright-running-fox.

Three things to do once, before you rely on this:

You can also ask Claude to "work in a worktree" mid-session and it creates one with the EnterWorktree tool. In the desktop app, pick the worktree option when starting a session.

One prompt you cannot suppress. When Claude tries to enter a path outside the repository's .claude/worktrees/ directory, Claude Code asks for your approval first — the move takes the session's working directory, write access, and project configuration such as CLAUDE.md and settings with it. An EnterWorktree permission rule and "don't ask again" both fail to suppress this; only bypassPermissions mode skips it. (Before v2.1.206, Claude could enter any existing worktree path without asking.)

Worktrees vs. subagents vs. agent teams vs. agent view

This is where most people go wrong, because four different features all get described as "running Claude in parallel" and they are not substitutes for each other. The useful way to think about it: three of them decide who coordinates the work, and worktrees decide who can touch which files. Worktrees are a layer, not an alternative.

FeatureWhat it actually parallelizesReach for it when
Worktrees File access. Each session or subagent gets its own checkout and branch. Two things that write code are running at once on one repository
Subagents Context. A delegated worker does a side task in its own context window and returns a summary. A side task would flood your main conversation with search results, logs, or file dumps you'll never reference again
Agent view (claude agents) Your attention. One screen dispatches and monitors background sessions. Research preview. You have several independent tasks and want to hand them off, glance at status, and step in only when one needs you
Agent teams Coordination. A lead splits a project, assigns it, and keeps workers in sync via a shared task list and messaging. Experimental, off by default. You want Claude to do the project management, not just the work
Dynamic workflows Scale. A script Claude writes runs many subagents and cross-checks their results. A job outgrows a handful of subagents — a codebase-wide audit, a 500-file migration, findings that need verifying against each other

Three combinations are worth knowing because they change what you have to do yourself:

There's also /batch, a bundled skill that splits one large change into 5 to 30 worktree-isolated subagents that each open a pull request. It isn't a fifth coordination style; it's a packaged combination of subagents and worktrees, and it's the fastest way to see whether this pattern suits your codebase without wiring anything up.

Finally: sessions in separate worktrees can still talk. Cross-session messaging lets Claude list and message your other sessions on the machine, so a finding in one worktree can be passed to another instead of you re-explaining it. Isolation of files does not mean isolation of knowledge.

Every one of these multiplies token usage. Four parallel sessions is roughly four times the spend, and each one re-reads your project from scratch. If your plan limits are already tight, read the token usage and costs guide before you scale this up.

Setting up the worktree environment

A worktree is a fresh checkout of tracked files. Your node_modules, your .venv, your .env — none of it is there. Two things to do:

Install dependencies. Ask Claude to do it as its first instruction in the new session, or run your project's setup yourself in the worktree directory. There's no automation for this; it's a fresh checkout and it behaves like one.

Carry gitignored config in with .worktreeinclude. Add the file to your project root. It uses .gitignore syntax:

# .worktreeinclude
.env
.env.local
config/secrets.json

Only files that match a pattern and are also gitignored get copied, so tracked files are never duplicated into the worktree. This applies to every worktree Claude Code creates with git: --worktree worktrees, subagent worktrees, and parallel sessions in the desktop app.

One pattern gotcha catches people. If you write a pattern starting with **/ and the files you want live inside a directory that is gitignored as a whole, Claude Code copies them only when that directory itself matches the pattern, or when the first name after the **/ is one of the names in the directory's path. So **/.claude/skills/*.md works — the first name is .claude, which is in the path. But **/config.json will not reach into an ignored vendor/ directory; write vendor/**/config.json instead. (Before v2.1.239, the reach-into-an-ignored-directory case was narrower still.)

You are copying secrets into more places. .worktreeinclude exists because most projects need real credentials to run, but the effect is that every parallel session gets its own copy of your .env. Keep the list minimal, keep .claude/worktrees/ gitignored, and don't put production credentials in a file you're duplicating across a dozen directories on disk.

How Claude Code enforces isolation

This is the part a plain git worktree add doesn't give you. Isolation by convention fails the first time an agent runs cd ../.. and then git reset --hard. While a session is isolated in a worktree, Claude Code blocks tool calls that would escape it. Four checks run, and they apply whether you started with --worktree, Claude entered a worktree with EnterWorktree, or you resumed a worktree session:

CheckWhat gets blocked
File edits An Edit, Write, or NotebookEdit targeting a path in the main checkout
Command working directory A Bash, PowerShell, or Monitor command whose working directory resolves to the main checkout — or that Claude Code can't verify stays outside it
Git redirects A command that points git back at the main checkout via git -C, --git-dir, GIT_DIR, GIT_WORK_TREE, or a cd before the git call
Command shape Any command where Claude Code can't verify from the command text that git stays inside the worktree — a computed command name, unparseable syntax. This check cannot be turned off.

The checks cover the repository you launched from, and also the main checkout that a linked worktree is linked from. Every subagent spawned from an isolated session inherits them, interactive or background. For PowerShell commands, only the working-directory check applies.

When a command is refused, Claude sees a tool error naming the worktree and telling it how to proceed — usually "split this into plain, separate commands." That's the fourth check firing on a clever one-liner, and the right response is a simpler command, not a workaround.

Practice parallel sessions before you trust them

Claude Lessons has free interactive lessons on running parallel sessions with worktrees, delegating to subagents, and reviewing diffs — practiced in a simulated Claude Code workspace where a bad command costs you nothing.

Start the free interactive lessons

Giving subagents their own worktrees

Subagents don't get worktrees by default, which is usually right — most subagents read and report rather than write. When several of them do write, parallel edits conflict, and worktrees fix it. Two ways to turn it on.

For a one-off, just say so: ask Claude to "use worktrees for your agents."

To make it permanent for a custom subagent, add isolation: worktree to its frontmatter in .claude/agents/:

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

Apply the requested refactor across every affected file, then run the tests
and report the results.

Each subagent gets a temporary worktree. Claude Code removes it automatically when the subagent finishes without changes; a worktree that still holds work stays on disk until the periodic sweep can remove it without losing anything. Subagent worktrees use the same base branch as --worktree, so they branch from your repository's default branch unless you set worktree.baseRef to "head" — which matters more than it sounds, and is the next section.

Controlling where worktrees branch from

The base branch

By default, new worktrees branch "fresh": from your repository's default branch on the remote, usually main. That's the right default for independent features, and the wrong one for the case people hit first — isolating subagents that need to work on the code you have in progress right now. Those subagents branch from main and never see your unpushed commits.

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

"head" branches from your current local HEAD instead, so the worktree carries your unpushed commits and feature-branch state. Inside a worktree, "head" resolves to that worktree's HEAD, not the main checkout's.

The setting takes only those two values — you can't point it at a branch name. To start a worktree from a specific existing branch, create it with git directly (see below). For a "fresh" base, Claude Code keeps origin/HEAD current: if the repository hasn't been fetched in the last 24 hours it fetches the default branch, capped at five seconds, falling back to the locally cached ref if that fails. With no remote configured, or no cached origin/HEAD it can fetch, the worktree falls back to your local HEAD anyway. (Before v2.1.208, a fresh worktree just used whatever origin/HEAD happened to be cached.)

Branching from a pull request

Pass --worktree a number prefixed with #, a GitHub pull request URL, or a GitLab merge request URL. Quote it, or your shell reads # as a comment:

claude --worktree "#1234"

Claude Code reads only the number, fetches that change's head commit from origin, and creates the worktree at .claude/worktrees/pr-1234. It picks the fetch path from origin's host: pull/<number>/head on github.com, merge-requests/<number>/head on gitlab.com, and on GitHub Enterprise, self-managed GitLab, or anything else it tries the GitHub path first and then the GitLab one. (Before v2.1.233, only #<number> and GitHub-style URLs were accepted.)

This is the best reason to use worktrees even if you never run parallel sessions: reviewing a colleague's PR in an isolated checkout, with Claude able to run the tests, without disturbing anything you have in progress.

Reusing a name

Passing a name whose directory already exists opens that worktree instead of creating a new one. With the default "fresh" base, a reopened worktree resets to the default branch rather than continuing at its old tip, but only when all three of these hold:

In every other case — any condition fails, the state can't be verified, worktree.baseRef is "head", or the name is a PR reference — it reopens at the old tip. Claude Code detects the merged case from git state alone: the remote branch it pushed to is gone, and every commit in the worktree is already on the default branch. (Before v2.1.208, reusing a name always reopened at the old tip.) The practical upshot: a worktree named bugfix that you keep reusing quietly becomes a fresh branch each time your last PR merges, which is what you want and a surprise the first time.

Cleanup: what disappears, what survives

When you exit an interactive worktree session, Claude checks the worktree for work that removal would destroy — changed files, untracked files, new commits — and behaves accordingly:

State on exitWhat happens
Clean, unnamed session Claude removes the worktree and its branch automatically
Clean, named session Prompts you first, so you can keep the worktree for later
Has work in it Prompts you to keep or remove. Removing deletes the directory and its branch, along with everything in them
Non-interactive -p run No exit prompt, so no cleanup. The worktree and the lock Claude Code took on it stay until a later session's stale-lock sweep

Separately, a periodic sweep removes worktrees Claude created for subagents and background sessions once they're older than your cleanupPeriodDays setting. The sweep leaves a worktree alone when it still holds work (changed files, untracked files, or unpushed commits), when it belongs to a --worktree session you haven't backgrounded, or when you created it with git worktree add — even if you later ran a --worktree <name> session in it and backgrounded that.

The mechanism behind that last one is worth knowing: Claude Code writes a marker into the git metadata of every worktree it creates, and the sweep keeps any worktree without one. (Before v2.1.246 the sweep didn't check for the marker and could remove a worktree you'd made yourself, if an old background-session record pointed at it.) While an agent or backgrounded session is running, Claude Code holds a git worktree lock on its worktree so cleanup can't remove it mid-flight — which also means git worktree remove refuses while it runs. The sweep releases locks belonging to sessions whose process has exited, so a killed background session doesn't strand its worktree forever (before v2.1.210 it did). It never releases a lock you set with git worktree lock.

To remove one by hand:

git worktree remove .claude/worktrees/feature-auth

# if it has uncommitted changes or untracked files
git worktree remove --force .claude/worktrees/feature-auth

# if git refuses because the worktree is locked
git worktree unlock .claude/worktrees/feature-auth

On Windows, removing a worktree doesn't delete files outside it: if a folder inside the worktree is an NTFS junction or directory symlink, Claude Code deletes only the link and keeps the target. (Before v2.1.205, a link nested in a subdirectory could take its target with it.)

Resuming a worktree session

Resume a session that was inside a worktree and Claude Code returns it there — for interactive resumes, for --continue and --resume in non-interactive -p mode, and for the Agent SDK. Before doing so it verifies the worktree is still a checkout genuinely separate from the main one, and declines if it isn't.

Three behaviors that surprise people:

When Claude enters or exits a git-created worktree, the transcript follows: Claude Code records the session under its new working directory the same way /cd does, so /desktop and --resume find it there. Exiting moves it back. This needs v2.1.198 or later, and a worktree made by a WorktreeCreate hook keeps its transcript at the launch directory instead.

An interactive resume that can't return to its worktree tells you so and continues without isolation. Non-interactive -p runs and Agent SDK resumes are stricter: every refusal except a missing worktree stops the resume with a stderr error rather than silently continuing unisolated. With --output-format stream-json the refusal also arrives on stdout as a result message with subtype error_during_execution, so an SDK application gets the reason and not just a non-zero exit. (Before v2.1.260, no result message was produced.)

Why the strictness difference matters. An interactive session continuing without isolation is visible to you — you'll see it. An automated pipeline continuing without isolation would silently start writing into your main checkout. Failing the resume is the correct behavior, and it's why you should never paper over a worktree resume error in CI with a retry loop.

What worktrees share with your main checkout

A worktree gets its own files and branch, but three things are deliberately shared — and each one saves you a chore:

All three hold whether the worktree came from --worktree, from git worktree add, or from the desktop app.

One thing that does not follow the worktree: hook paths. ${CLAUDE_PROJECT_DIR} stays pointed at the project root where the session started, so a hook command like ${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh still runs the script in the main checkout. The worktree path arrives a different way: the cwd field in the hook's input JSON is the worktree root, and it moves again when Claude runs cd. If a hook needs to act on the worktree, read cwd — don't assume CLAUDE_PROJECT_DIR.

When you want a worktree on a specific existing branch, or somewhere outside the repository, use git directly:

# new branch
git worktree add ../project-feature-a -b feature-a

# existing branch
git worktree add ../project-bugfix fix-issue-456

cd ../project-feature-a && claude

git worktree list
git worktree remove ../project-feature-a

Troubleshooting the errors you'll actually hit

Git LFS files are pointer files in the worktree

The most common real-world complaint, and it has a specific cause: you ran git lfs install --local, which writes the LFS filter driver into the repository's own .git/config rather than your global git config. Claude Code deliberately skips the repository's own filter drivers when creating a worktree — a filter driver is a shell command, and anything that can write to the repository (including Claude) could have put one there. A plain global git lfs install is unaffected. Fix: run git lfs pull inside the worktree. (Before v2.1.247, Claude Code ran those drivers during creation.)

In four rarer cases, Claude Code refuses to create the worktree at all rather than guess. Match the error:

Error mentionsFix
Could not read the repository git config Fix the permissions on .git/config and retry
A filter driver whose name cannot be neutralized (contains = or a newline) Rename or remove that filter driver in .git/config
A conditional include (includeIf) Inline what the includeIf pulls in, remove the includeIf, retry. An includeIf in your global config doesn't trigger this
The repository's own git config sets <key> The named key points Git LFS at a program to run (e.g. lfs.standalonetransferagent). If it's yours, move it to your global config. If you don't recognize it, remove it — a tool or checkout you don't trust may have written it

"Refusing to use <path> as an isolation worktree"

Claude Code checks a directory's git identity before adopting it as an isolated checkout. The usual cause is that the directory's git metadata resolves back into the main checkout — its .git file points at the main repository's own .git directory, or a core.worktree redirect resolves its working tree to the main checkout. From such a directory, an ordinary git reset --hard would hit your main checkout. Claude Code also refuses when a .git entry exists but can't be read, rather than assuming it's fine.

The endings that need different responses:

In all cases Claude Code leaves the refused directory on disk, because it may hold work. Salvage before you delete.

Worktree creation fails on a symlinked path

Claude Code refuses to create a worktree when .claude, .claude/worktrees, or the worktree directory itself is a symlink, and the error names the path. Remove the symlink and retry. This is a hardening fix: before v2.1.212, a committed symlink at one of those paths was followed, and worktree creation could write files outside the repository.

Non-git version control

For SVN, Perforce, or Mercurial, configure a WorktreeCreate hook that creates the checkout and prints the directory path on stdout, plus a WorktreeRemove hook to clean up:

{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
          }
        ]
      }
    ]
  }
}

Three consequences of replacing the git logic, all of which have bitten people:

If Claude Code can't enter the worktree directory at startup it prints an error naming the path and exits with code 1 — most often because the hook printed something other than the directory it created, or the directory was deleted after setup.

Common mistakes

FAQ

How do I run two Claude Code sessions at once?

Run claude --worktree <name> in one terminal and claude --worktree <other-name> in another. Each gets a git worktree under .claude/worktrees/<name>/ on a new branch worktree-<name>. Edits in one never touch the other, and Claude Code blocks tool calls that would reach back into your main checkout. You can just open two terminals in the same directory — but then both sessions edit the same files, and you'll get exactly the collisions worktrees exist to prevent.

Where does Claude Code put worktrees?

Under .claude/worktrees/<name>/ at your repository root. Add .claude/worktrees/ to .gitignore. A worktree branched from a pull request with --worktree "#1234" lands at .claude/worktrees/pr-1234 instead. To place them elsewhere, use a WorktreeCreate hook, which replaces the default git logic entirely.

Does my .env get copied into a worktree?

Not by default — a worktree is a fresh checkout, so gitignored files are absent. Add a .worktreeinclude file to your project root listing what to carry in. It uses .gitignore syntax, and only files that both match a pattern and are gitignored are copied, so tracked files are never duplicated. It applies to --worktree worktrees, subagent worktrees, and desktop parallel sessions — but not when a WorktreeCreate hook replaces worktree creation.

Can subagents run in their own worktrees?

Yes. Ask Claude to "use worktrees for your agents" for a one-off, or add isolation: worktree to a custom subagent's frontmatter to make it permanent. Claude Code removes the temporary worktree when the subagent finishes without changes; one that still holds work survives until the cleanup sweep can remove it safely. See the subagents guide for when delegation is worth it at all.

Do I still need worktrees if I'm using subagents or agent teams?

They solve different problems. Subagents protect your context window, not your files. Agent teams coordinate sessions but don't isolate teammates in worktrees, so you have to partition work by file yourself. Worktrees are the file-isolation layer underneath: use them whenever two things that write code run at once on one repository.

How do I clean up Claude Code worktrees?

Exiting an interactive session handles most of it: a clean unnamed session's worktree is removed automatically, and anything else prompts you. Headless -p runs never clean up. By hand: git worktree remove <path>, with --force if it has uncommitted changes, and git worktree unlock <path> first if git refuses because the worktree is locked.

Why are my Git LFS files just pointers in the worktree?

Because git lfs install --local wrote the LFS filter driver into the repository's own .git/config, and Claude Code skips repository-defined filter drivers when creating a worktree — a filter driver is a shell command, and anything that can write to the repo could have put one there. Run git lfs pull inside the worktree. A global git lfs install isn't affected.

Learn the habits, not just the flags

Claude Lessons has 38 free interactive lessons — parallel sessions with worktrees, delegating to subagents, reviewing diffs, recovering from drift — all practiced hands-on in a simulated Claude Code workspace with instant feedback.

Practice hands-on in the free interactive lessons