Parallel Claude Code: Worktrees and Named Sessions Without the Chaos
Once an agent is fast enough to be worth running, you want to run more than one — a refactor in this tab, a copy fix in that one, a spike in a third. Then they start stepping on each other, and it feels like the agents are the problem. They aren't. The problem is that they're all reaching into the same drawer.
Here's the setup that got me into trouble. I had two Claude Code sessions open against the same repo. One was halfway through a pricing tweak — uncommitted edits sitting in the working tree. The other needed to do something unrelated on a feature branch. Both perfectly reasonable tasks. Both pointed at the same checkout.
A git repository has exactly one working tree and one checked-out HEAD. Two agents writing into it is two writers sharing one mutable surface, and that has a name: a race. No prompt fixes it, because it isn't a reasoning failure — it's a structural collision.
Watch it happen
Let me make it concrete instead of hand-waving. Agent A is mid-task: a discount it's testing, not yet committed.
$ # Agent A: uncommitted edit on main
$ git status --short
M pricing.js
Agent B, in the other tab, just wants to switch to its own branch to get started:
$ # Agent B, same checkout:
$ git switch promo-banner
error: Your local changes to the following files would be overwritten by checkout:
pricing.js
Please commit your changes or stash them before you switch branches.
Aborting
Best case, B is simply blocked — git refuses, because moving branches would overwrite A's work. But an agent that wants to make progress doesn't stop at "blocked." It does the obvious thing to get unstuck:
$ git stash # B clears the way so it can switch
$ git switch promo-banner
$ cat pricing.js
export const price = 100;
export const banner = "Sale!";
And there it is. A's in-progress discount — the price = 80 edit it was actively reasoning about — has silently evaporated from the working tree. It isn't lost forever (it's in a stash entry nobody's tracking), but as far as Agent A's next step is concerned, the file just changed under it for no reason it can see. The session is now operating on a lie.
The real failure
It's tempting to read this as "the agent shouldn't have stashed." But any sequence that unblocks a branch switch — stash, commit, checkout -f — mutates state another session is depending on. The bug isn't the command. It's that two tasks share one working tree at all.
One working tree per task
The fix isn't discipline, it's topology. Git already has the right primitive: a worktree is a second working directory backed by the same .git repository, with its own checked-out branch. Two worktrees, two branches, zero shared mutable surface.
Same starting point — A is mid-edit on main. This time B gets its own checkout instead of switching in place:
$ git worktree add ../app-promo promo-banner
$ git worktree list
/private/tmp/app 09b8722 [main] # A, still mid-edit
/private/tmp/app-promo f92af58 [promo-banner] # B, its own dir + branch
$ # A's working tree, completely untouched:
$ git -C /private/tmp/app status --short
M pricing.js
$ sed -n 1p /private/tmp/app/pricing.js
export const price = 80; // A is testing a discount
A's discount is right where it left it. B is off in its own directory on its own branch. Neither can clobber the other because there's nothing in common to clobber.
Claude Code builds this directly into how you launch a session. Instead of starting in your main checkout, you start the session in a worktree:
claude -w promo-banner # start this session in a fresh worktree
claude -w promo-banner --tmux # …and give it its own tmux / iTerm session
-w/--worktree creates the worktree and runs the session there; --tmux spins it into its own terminal session so you can literally see your tasks side by side. There's a subtlety in how the worktree gets its gitignored files (like .env) — I went down that rabbit hole in a separate post on the WorktreeCreate hook — but the headline is simple: each task gets a clean room.
Tell the agent the rule out loud
The topology only holds if the agent respects it. Left to its own judgment, an agent in a pinch will still reach for git switch or git stash — the very moves that caused the collision. So I make the rule explicit in CLAUDE.md, where it's loaded into every session as a standing instruction:
# Branch isolation
Never change branches in this working tree with `git checkout` or
`git switch`, and never run `git stash`. If a task needs a different
branch, create a git worktree for it and work there instead.
It reads like a small thing. It's the difference between an agent that quietly rearranges your working tree to unblock itself and one that stays in its lane.
Naming: so you can find your way back
Isolation solves collisions. It creates a second problem: now you have three, five, eight sessions, and they all look alike. By default Claude Code auto-generates a name for each one, so --resume hands you a list of machine-written summaries and dares you to remember which was the auth refactor.
The cure is to name the session when you start it:
claude -n "refactor-auth"
claude -w promo-banner -n "promo-banner-copy" # name + worktree together
That name isn't cosmetic. It's written straight into the session log as the very first record — I went and looked:
$ head -1 ~/.claude/projects/<project>/<id>.jsonl
{"type":"custom-title","customTitle":"refactor-auth","sessionId":"9a8b0d2b-…"}
Because the title is persisted, it's there waiting when you come back. If you forgot to name a session at launch, /rename sets the same custom-title mid-flight. Then picking the work back up is two flags:
claude --continue(-c) — jump straight back into the most recent session in this directory. The "I just stepped away for coffee" path.claude --resume(-r) — choose from this project's past sessions by name. The "which one was the auth work" path — and the reason naming pays for itself the moment you have more than two.
The whole loop
Put together, running parallel agents stops being chaotic and becomes boring, in the good way:
- Start each task isolated and named:
claude -w <branch> -n "<what-it-is>". Its own worktree, its own label. - Forbid in-place branch moves in
CLAUDE.mdso nothing reaches into a shared tree. - Walk away. Three tasks can run at once without ever touching each other's files.
- Come back with
--resumeand pick the one you want by name.
The instinct, when parallel agents trip over each other, is to slow down and run one at a time. You don't have to. Give each task its own working tree and its own name, and the collisions you were managing by hand simply stop existing.