Picture this. You're deep in a Claude Code session, building a feature. It's going well. Then Slack lights up: production is on fire, and you're the one holding the extinguisher.
So you git stash, open a bug-fix branch, and pray you remember what "WIP: temp fix DO NOT COMMIT" meant three days from now. Or worse, you open a second terminal in the same checkout and now two AI sessions are quietly editing the same files like two chefs reaching into the same pot of soup.
Neither is a great time. There's a much less chaotic option, and it's been sitting in git this whole time: worktrees. Claude Code has built-in support for them, and once you start using them, going back feels like coding with your shoelaces tied together.
Here's what I've learned running this in production, gotchas and all.
The concept: one repo, several apartments
A git worktree is a separate working directory with its own files and its own branch, but all worktrees share the same repository history and remote as your main checkout. Think of it less like cloning the repo and more like giving each task its own apartment in the same building. Same address, same plumbing, different rooms, nobody's stepping on anybody's laundry.
project/
├── main/ ← checked out on default branch
└── .claude/worktrees/
├── feature-auth/ ← own files & branch
└── bugfix-123/ ← own files & branch
shared .git history & remote
Three things fall out of this setup:
- Own files & branch. Each worktree checks out its own branch, blissfully unaware the others exist.
-
Shared history. All worktrees point at the same
.gitobject database, so spinning up a new one is basically free. No cloning, no waiting, no coffee break required. - No collisions. Edits in one Claude Code session never touch files in another session's worktree. Your feature branch and your hotfix can coexist without ever finding out about each other.
Why it matters
Without worktrees, it's one session at a time, in one checkout, like a single bathroom in a house full of people who all wake up at 7am. You stash to switch tasks. A second session risks reading half-edited files from the first. Every switch means rebuilding your mental model from scratch, which is its own special kind of exhausting.
With worktrees, multiple sessions run at once, each in its own little bubble. A feature build and a hotfix move forward at the same time, each session seeing only its own file state. You can walk away from one and come back later to find it exactly as you left it, no archaeology required.
If you want proof this actually scales, look at Boris Cherny. He's a well-known engineer who reportedly hasn't hand-written a line of code in months. Instead he manages parallel AI "loops" that write, test, and run code while he plays strategic director. He's talked about running five Claude Code sessions in parallel and shipping around thirty pull requests a day. That's not a typo, and yes, worktrees are a big part of what keeps that many parallel sessions from turning into a demolition derby.
Getting started
You don't need to memorize a magic incantation for this. Just tell Claude what you want, in whatever words come naturally, something like "start this in a new worktree called feature-auth," and it handles the rest.
Prefer typing commands directly? The flag version does the same thing:
$ claude --worktree feature-auth
This creates .claude/worktrees/feature-auth/ on a new branch. Run it again with a different name (or a differently worded request) in another terminal for a second isolated session, or skip the name entirely and let Claude generate one for you. Mine once came out as bright-running-fox, which honestly felt like a compliment.
One catch: the first run in a new directory needs workspace trust, so run claude once to accept it before you get too excited.
And you don't need to relaunch mid-session either. Ask Claude to "work in a worktree" while you're already in the middle of something, and it quietly spins one up using the EnterWorktree tool without making you start over.
Practical tip: add .claude/worktrees/ to your .gitignore, unless you enjoy git status yelling at you about a folder full of parallel universes.
Safety: how isolation is actually enforced
This is the part people underestimate, right up until they don't. While a session is inside a worktree, Claude Code actively blocks tool calls that would reach back into the main checkout, and that goes for the session itself and every subagent it spawns.
- File edits. Blocks an Edit, Write, or NotebookEdit aimed at a path inside the main checkout.
- Command working directory. Blocks a shell command whose working directory resolves to (or can't be verified outside) the main checkout.
-
Git redirects. Blocks git commands sneakily redirected into the main checkout via
-C,--git-dir,GIT_DIR/GIT_WORK_TREE, or a priorcd.
When Claude blocks something, it doesn't just silently shrug. It tells you exactly which worktree got in the way and how to proceed, so you're not left debugging a mystery.
My advice: the guardrails are only for what Claude does. If you go rogue and run git commands yourself against the wrong directory, you can absolutely still make a mess. The safety net has a Claude-shaped hole in it.
Lifecycle: cleaning up when you're done
On exit, Claude checks the worktree for anything removal would delete (changed or untracked files, new commits) before deciding what to do with it:
| Session type | Behavior |
|---|---|
| Clean, unnamed session | Removed automatically, no questions asked |
| Clean, named session | Prompts first, since you gave it a name and presumably cared |
| Work still in it | You decide: keep the directory and branch, or remove everything |
Non-interactive (-p) |
No auto cleanup, you're on your own with git worktree remove
|
⚠️ Important: if you haven't pushed a worktree's commits, removing it can erase that work permanently. A local commit does not save you. Push before you remove, especially for named sessions you're about to clean house on.
Configuration: customize how worktrees are created
A few knobs worth knowing about:
-
Base branch.
"fresh"(the default) starts from the remote's default branch."head"branches off your current local HEAD, carrying over unpushed work. Set it withworktree.baseRef: "head". -
Branch from a pull request.
--worktree "#1234"builds a worktree straight from a PR, no manual checkout required. -
Carry in gitignored files. A
.worktreeincludefile lets you copy gitignored files, like env vars or secrets, into every new worktree automatically, so you're not hunting them down each time.
Going further: isolating subagents
Subagents can get their own worktrees too, so parallel edits don't collide behind your back. Just ask, in your own words, something like "use worktrees for your agents," or bake it into a custom subagent's frontmatter so it's the default.
Cleanup here is automatic as well. A subagent worktree with no changes disappears the moment it finishes. One with actual changes sticks around until a periodic sweep can remove it safely. While an agent is mid-task, Claude locks its worktree so cleanup can't yank the rug out from under it, and the sweep never touches anything you created yourself with --worktree. It cleans up after itself, not after you.
Reference: managing worktrees manually with git
Sometimes you want a specific existing branch, or a worktree living outside the repository entirely. Plain git handles that fine:
# Create on a new branch
git worktree add ../project-feature-a -b feature-a
# Create from an existing branch
git worktree add ../project-bugfix fix-issue-456
# Start Claude there
cd ../project-feature-a && claude
# List all worktrees
git worktree list
# Remove one
git worktree remove ../project-feature-a
A genuinely useful IDE tip: in IntelliJ / Android Studio, if a branch is already checked out in a worktree, you won't get a normal checkout or pull option, and the IDE will not explain why. Right-click it in the branch list and choose Open Worktree to teleport straight there instead.
Applying it to a real project
On our own project, two files are gitignored and vanish every time a fresh worktree gets created, like clockwork.
-
App config (
apps/users_app/assets/config.json), which holds the environment URLs. Instead of copying it by hand every single time, we just tell Claude what it needs to contain and let it write the file directly. No special syntax, no fumbling for the old copy. -
Firebase's
GoogleService-Info.plist, also untracked. This one gets copied from the main checkout (or a secrets manager) into the same path inside the worktree.
We also update the IDE's Run/Debug configurations so the Dart entrypoint points at the worktree's copy of main.dart instead of the main checkout, for both iOS and Android, since each has its own entrypoint field lurking in a slightly different place. Everything else, build flavor, run args, stays exactly the same as the main checkout's.
None of this is unique to us. Any project with gitignored config or IDE run configs pointing at absolute paths is going to need the same two fixes.
Recap
-
Launch fast. Ask Claude to start in a new worktree, or type
claude --worktree <name>yourself if you like the ceremony of a command line. - Switch mid-session. Just ask Claude to "work in a worktree." No restart, no drama.
- Run tasks side by side. A feature build and a hotfix can move forward at the same time, safely, without one eyeing the other suspiciously.
- Let cleanup happen. Unnamed, clean sessions tidy up after themselves on exit, so your disk doesn't slowly fill with abandoned branches.
Worktrees look like a minor convenience right up until you're running two or three Claude Code sessions at once. That's the moment they stop being a nice-to-have and start being the only thing standing between you and a repo full of collisions.
Full reference: code.claude.com/docs/en/worktrees
What project-specific boilerplate have you had to fight when adopting worktrees? Gitignored secrets, native build artifacts, IDE configs that refuse to cooperate? I'd genuinely like to hear how others have worked around it.
Top comments (0)