Git Worktree: How to Run Parallel Claude Code Sessions Without Them Fighting Over the Same Files
Picture this: you’ve got Cursor open, or VS Code, or yet another terminal session running. You start a plan, you start discussing it with Claude Code, and Claude Code starts doing the work.
We’re not living in the “one thing at a time” era anymore. Unfortunately or not, we’re in the multi-thread era. And if you’re in this era, while Claude Code is coding, you want to do something else.
So what happens, typically? You open yet another tab, or another terminal, in exactly the same folder, the same branch — and you start prompting again.
Ten sessions, one folder, and everyone’s fighting
Here’s the problem. Between two different sessions — or why not even ten, which is the number of sessions I sometimes have running in a single project — they start fighting against each other. One session wants to edit a file the other session is also touching, and vice versa.
To be candid, first: Claude Code is very good at this. It handles the merges, it handles the conflicts — something I remember well from when I was a developer and the repository wasn’t GitHub, it was SVN, if I remember right. Holy shit, it was a fucking nightmare. Claude Code handles everything perfect, by comparison.
But there’s a better way to do this. And the better way is called worktree. That’s the only name you need to remember. And it’s way easier to use than the name makes it sound.
The magic sentence
It’s always the same story with these things — you can wire it into a skill, or just dictate it, type it, whatever you want, straight into your plan. The trick is one sentence:
Please develop the plan in a different worktree. Once you’re done, commit and push the worktree to my branch, then delete the worktree. If needed, please fix and resolve all conflicts first, then commit.
What’s actually happening behind the scenes
Behind the scenes, Claude Code creates another folder — and it’s not exactly cloning your repository, because a worktree works in a slightly different way. It’s a second working tree linked to the same repo, tracking only the differences.
Inside that folder, there’s no conflict, none of the fighting — it’s isolated. It’s even visible in my setup: I added the worktree name as a status in the footer of my terminal, so I always know which one I’m sitting in.
Once the work is done, commit and push adds everything back into the main tree, merging it all together.
Four plans, four worktrees, one merge
This is where it actually pays off. While I’m doing this, I’ll have Claude Code executing four different plans in four different worktrees at the same time. I don’t have to babysit any of them — I just know I need to do something once all the branches come together in one place. Main, in my case. Or another pull request, another branch.
I turned it into a skill: work-isolated
Back at the magic sentence, I said you could just wire this into a skill. I did — it’s a user skill I built called work-isolated. It exists to force the discipline when I’d otherwise forget it halfway through a plan. It doesn’t reimplement the worktree commands; it orchestrates them.
One line captures it: write inside the worktree, verify on the base branch. It opens the worktree, does all the work in there, merges it back into the branch I started from, deletes the worktree, and only then runs the tests — where the dev server actually serves the merged code. It never pushes and never opens a PR. That’s still my call, later.
There’s one non-obvious rule baked in, and it cost me a debugging session to learn: never run the tests inside the worktree. The worktree inherits the copied .env, so it points at the same dev port as the base repo. Playwright reuses an already-running server in local — so the suite quietly runs against the base repo without your changes, and passes. A silent false green. So: write in the worktree, test after the merge, on the base.
Here’s the skill, more or less:
---
name: work-isolated
description: Run an ENTIRE task inside an isolated git worktree, then merge it back
into the base branch locally, test it THERE, and destroy the worktree — before
returning to the user. Never pushes, never opens a PR. Orchestrates the worktree
commands; doesn't reimplement them.
---
# /work-isolated [task | slug]
Write inside the worktree, verify on the base branch.
## The one hard invariant
Never push, never open a PR. The only commit on the base branch is the merge commit
from closing the worktree. Push/PR are the user's call, later, explicitly.
## Procedure
1. Open the worktree. If you're already in one, don't nest — work here. Put "close the
worktree" as the final task now: never return with a worktree still open.
2. Write ALL the code inside the worktree — implementation, refactor, docs, fixtures.
Gotcha: editing with the BASE repo's absolute paths writes to the base and breaks the
isolation. Use the worktree's paths, and check the changes actually land there.
3. NO tests, build, or dev server inside the worktree. The worktree inherits the copied
.env → the SAME dev port, and Playwright reuses the base's running server in local
(reuseExistingServer = !CI) → it tests the base repo without your changes = a silent
false green. Write code here; verify after the merge.
4. Close by merging into the base branch you started from (main OR a feature branch):
commit what's pending, merge --no-ff, resolve conflicts (stop and ask if risky), then
remove the worktree and its branch.
5. NOW run the test gate — on the base branch, where the server serves your merged code,
so the result is real. Red because of your change? Fix it here (nothing was pushed).
Red for a pre-existing reason? Say so — never pass a red off as green.
6. Self-check, then report: what was done, that it's merged into <base> locally (no push),
the worktree is gone, and the honest test result.
Then I stopped trusting myself to remember
The skill worked. Too well, actually — well enough that I started noticing the gaps: the times I forgot to type the magic sentence, or skipped it because I was in a hurry. A discipline that depends on me remembering isn’t really a discipline. So I wired it into a hook.
It lives at the user level, not per-project: ~/.claude/settings.json plus a small script, ~/.claude/hooks/auto-work-isolated.sh. That’s the part that matters — it applies to every repo I open, not just the one product I happened to set it up in.
It’s registered as a PostToolUse hook, matched on EnterPlanMode|ExitPlanMode:
{
"hooks": {
"PostToolUse": [
{
"matcher": "EnterPlanMode|ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "bash \"$HOME/.claude/hooks/auto-work-isolated.sh\"",
"timeout": 10
}
]
}
]
}
}
It fires twice, with two different messages injected depending on where I am. On EnterPlanMode, while the plan is still being written, it tells Claude the plan will be executed in isolation — so write it that way from the start. Test gate is the last step, and it runs after the merge, on the base branch, never inside the worktree. No push, no PR steps. Handover docs go in their real project folder, not somewhere temporary. And explicitly: don’t open the worktree yet, nothing gets written during plan mode.
On ExitPlanMode, once I’ve approved the plan, the second message tells Claude to invoke work-isolated right now and run the whole execution through it — open the worktree, write everything there, never touch tests or the dev server inside it, close by merging into the base branch, delete the worktree and its branch, then run the test gate where it’s actually real. It also forces “close the worktree” onto the todo list as an explicit last item, so a session can’t quietly end with one left open.
There are escape hatches, because a hook that gets in your way is worse than no hook. A file at ~/.claude/.no-auto-work-isolated turns it off everywhere; one at <repo>/.claude/.no-auto-work-isolated turns it off for a single project. It stays silent outside a git repo, or if I reject the plan. Any internal error, and it exits clean with no output — it never blocks the session. Two exceptions live inside the injected instructions themselves: if I’m already inside a worktree, don’t nest another one, just finish there and close normally; and if the task is obviously too small for this — one line, read-only, docs-only — say so out loud and skip it. Default is still isolate.
I don’t type the magic sentence anymore. I don’t have to remember the skill exists. Plan mode is the trigger now.
Four plans, four worktrees, and work-isolated closing each one back into main for me — I just show up at the end for the one merge that matters.
That’s the trick. Not flashy, just one word — worktree — but it can genuinely save you from stepping on your own work, and let you run as many parallel sessions as you want without ever looking over your shoulder.
I love feedback.
Did this help? One tap — that's it.
Thanks 🙏
Want to add anything — or get in touch? Totally optional.
Got it — thank you. 🙏