Claude Code worktrees: run three sessions, keep the thread
How --worktree gives each Claude Code session its own checkout, what to copy into it, how cleanup works, and how to know which of three sessions needs you.
We run three Claude Code sessions most of the day: one fixing, in its own worktree; one testing the app on the Mac; one planning and reviewing. Worktrees solved the first problem that setup has, two sessions editing the same file. They do nothing for the second, knowing which of the three has stopped and what you were doing there when you left. This is how we run them, from the Claude Code worktrees documentation on 22 September 2026 and from a week of doing it.
Start a session in a worktree
claude --worktree fix-hover
--worktree (or -w) with a name creates a git worktree, a separate working directory with its own branch that shares the repository’s history, and starts Claude in it. The worktree goes under .claude/worktrees/<name>/ at the repository root, on a new branch named worktree-<name>. Run the same command with another name in another tab and you have two sessions that cannot touch each other’s files. Leave the name out and Claude Code invents one like bright-running-fox.
Two things to do once:
- Add
.claude/worktrees/to.gitignore, or every worktree shows up as untracked files in the main checkout. - Run
claudeonce in the repository first and accept the trust dialog.--worktreerefuses to start in a directory you have not trusted (non-interactive-pruns skip the check).
You can also ask a running session to “work in a worktree”; it creates one with its EnterWorktree tool and moves there. Entering a path outside .claude/worktrees/ asks for your approval, because the session’s working directory, write access and CLAUDE.md all move with it.
What is not in a worktree
A worktree is a fresh checkout. Tracked files are there; everything gitignored is not. The first session we started this way spent its first ten minutes discovering there was no .env.
Secrets and local config. Put their names in a .worktreeinclude file at the project root, gitignore syntax. Claude Code copies matching gitignored files into every worktree it creates, whether from --worktree, a subagent, or the desktop app:
.env
.env.local
config/secrets.json
Dependencies and build output. node_modules, .build, target are not copied and should not be. Install per worktree, or ask the session to. The trap is the other direction: each worktree gets its own build folder, and a Swift .build is 2 to 4 GB. Six worktrees filled a disk here on 17 September; our done script now deletes .build before it removes the worktree.
Hooks. ${CLAUDE_PROJECT_DIR} in a hook command still points at the main checkout after Claude enters a worktree. The hook’s stdin JSON carries cwd, which does follow the worktree. Read that when a hook needs the worktree path, which matters below.
Which branch it starts from
By default a new worktree branches from the remote’s default branch, usually main, fetched if the repository has not been fetched in the last 24 hours. That is what you want for a fix: a clean tree that matches what will be merged. To branch from your current local HEAD instead, unpushed commits included, set it in settings.json:
{
"worktree": {
"baseRef": "head"
}
}
baseRef takes only "fresh" or "head", not a branch name. To start from a specific existing branch, create the worktree with git and start Claude inside it:
git worktree add ../project-bugfix fix-issue-456
cd ../project-bugfix
claude
To review a pull request in isolation, pass its number with # (quoted, or the shell eats it) or its URL. Claude Code fetches the PR head from origin into .claude/worktrees/pr-<number>:
claude --worktree "#1234"
Reusing a name opens the existing worktree. With the default "fresh" base, a reopened worktree that is clean, still on its own branch and already merged resets to main instead of continuing at its old tip.
What the session is not allowed to do
While a session is in a worktree, Claude Code blocks four kinds of tool call: an Edit or Write that targets a path in the main checkout; a Bash command whose working directory resolves to the main checkout; a git command redirected into it through git -C, --git-dir, GIT_DIR or a cd; and a command whose text it cannot verify stays inside the worktree, such as a command name computed at runtime. Claude sees each refusal as a tool error that names the worktree and says how to rewrite the command. The same rules cover every subagent the session spawns.
Subagents can get their own worktrees too. Ask for “use worktrees for your agents”, or make it permanent for a custom subagent with isolation: worktree in its frontmatter. A subagent worktree that ends with no changes is removed at once; one with changes stays until a periodic sweep can remove it without losing work.
Cleanup
When you exit an interactive worktree session, Claude Code looks at the worktree before removing anything: changed or untracked files, uncommitted work in submodules, new commits.
| the worktree is | what happens |
|---|---|
| clean, session unnamed | removed with its branch, no question |
clean, session named with /rename | asks, so you can keep it for later |
| has work in it | asks: keep (directory and branch stay) or remove (everything in it goes) |
| cannot be inspected | asks, and names what it could not check |
-p runs have no exit prompt and never clean up. For those, and for anything the sweep left behind:
git worktree list
git worktree remove .claude/worktrees/fix-hover # --force if it has uncommitted changes
git worktree unlock .claude/worktrees/fix-hover # first, if a killed session left a lock
Claude Code holds a git worktree lock on a worktree while its agent runs, so a concurrent cleanup cannot remove it. A killed background session’s lock is released by the next sweep; a lock you set yourself never is.
What worktrees share with the main checkout is worth knowing: the .git directory (commits from inside a worktree work with the sandbox on), project-scope plugins, and permission approvals. Choosing “don’t ask again” for a Bash command in a worktree session saves the rule to the main checkout’s .claude/settings.local.json, so it applies in every worktree and survives the worktree’s removal.
Name the tabs
Three terminal tabs that all say claude are three identical doors. /rename <what this session is for> gives the session a name; resume finds it by that name later, and a named session gets the cleanup prompt instead of silent removal. We name each tab after the row on our task board it is working, so a glance at the tab bar says what is in flight.
Claude Code also writes its state into the terminal window title, a spinner while it works. Ghostty, iTerm2 and Terminal all show it. That is the only built-in signal that a session in the background has stopped, and it is one character wide.
The problem worktrees do not solve
Files no longer collide. Attention still does. You give the fixing session a task, switch to the testing session, get pulled into a Slack thread, and twenty minutes later the fixing session has been sitting on a permission prompt for nineteen of them.
The first fix is a Notification hook that puts the worktree folder in the banner title, so three sessions send three different notifications. The full setup, with the macOS gotcha that makes half of these fail silently, is in Claude Code notification when done. The short version reads cwd from stdin:
#!/bin/sh
input=$(cat)
dir=$(printf '%s' "$input" | jq -r '.cwd // "" | split("/") | last')
osascript -e "display notification \"needs you\" with title \"Claude Code · $dir\""
A hook fires per session, for Claude Code only. It does not know that Codex in the fourth tab is also waiting, that the session you left an hour ago has been idle since, or which Slack thread you were reading when you switched away. It lands you in a terminal, not on the thought.
The layer over the tabs
That gap is what Sharpy is for. It installs its own small hooks into Claude Code (six events: session start, your prompt, a notification, the turn stopping, the turn failing, session end), Codex and Cursor, each a guarded line that runs sharpy flow signal and cannot break the agent. Every agent window becomes a lane in its Flow queue, and each lane’s row says in plain words what it wants from you and for how long: Needs you · 4m, Needs a yes, Working · 12m, Stopped with an error, Agent closed — answer is still here. Codex has no notification event, so Sharpy reads that one off the screen instead and says unknown until it has looked. Go brings the right window forward. And because Sharpy remembers the text of what you had on screen, the question “what was I doing before I switched to this tab” has an answer with a source.
If you run several sessions at once, join the waitlist and say so in the reply to the welcome email. How Claude Code reads the same memory over MCP is in Claude Code memory: what it keeps, and how to add your screen.
Questions
How do I run multiple Claude Code sessions on the same repo?
Start each one with claude --worktree <name> in its own terminal tab. Claude Code creates a git worktree under .claude/worktrees/<name>/ on a branch called worktree-<name>, so the sessions edit separate files and never collide. Add .claude/worktrees/ to .gitignore.
Where does Claude Code put worktrees, and which branch do they start from?
Under .claude/worktrees/ at the repository root. By default a new worktree branches from the remote default branch, usually main. Set worktree.baseRef to "head" in settings to branch from your current local HEAD instead, including unpushed commits.
Why are my .env files missing inside a worktree?
A worktree is a fresh checkout, so gitignored files are not there. Put their names in a .worktreeinclude file at the project root (gitignore syntax) and Claude Code copies them into every worktree it creates. Dependencies and build folders still have to be installed per worktree.
What happens to the worktree when I exit the session?
Claude Code checks it for uncommitted files and new commits. A clean unnamed worktree is removed with its branch; a named one asks first; one with work in it asks whether to keep or remove. Sessions run with -p never clean up, so remove those with git worktree remove.
How do I know which of my parallel sessions has finished?
A Notification hook fires per session and can put the worktree folder in the banner title. It cannot tell you that Codex in the next tab is also waiting, or what you were doing when you left. Sharpy reads the state of every agent window and shows which one needs you.