Any professional developer using Claude Code should be using worktrees. They isolate the work and allow each agent to properly verify what it's doing. No more agents tripping over each other's changes, no more wondering which file belongs to which task.
What is a worktree?
For anyone who hasn't used them before, a worktree is essentially an isolated branch. When Claude Code creates a worktree, it creates a new branch off the main branch and duplicates the entire directory. You get a completely separate working copy of your project, with its own files, its own changes, and its own branch.
That means you can have one agent working on a feature in one worktree while another agent fixes a bug in a different worktree, and neither of them interferes with the other.
The gotchas
Worktrees are brilliant, but there are two things that catch people out:
- Gitignored files are not copied. Your .env, .env.local, secrets configs, anything in your .gitignore simply won't be there in the new worktree.
- Dependencies are not copied either. Your node_modules directory doesn't come along for the ride. The worktree is a fresh checkout, so there's nothing installed.
Both of these will cause your agents to fail if you don't address them upfront.
Copying gitignored files
Claude Code has a .worktreeinclude file for exactly this. Drop it in your project root and list which gitignored files should be copied into worktrees. It uses .gitignore syntax, and only copies files that match a pattern and are also gitignored. Tracked files are never duplicated.
.env
.env.local
That's it. Any file matching those patterns that's gitignored gets copied into every new worktree automatically.
Installing dependencies automatically
There are a few ways to handle this, but the most reliable approach I've found is a Claude Code hook on SessionStart. The hook calls a shell script that checks if you're in a worktree, checks if node_modules already exists, and if not, runs your package manager's install command.
First, the hook config in your .claude/settings.json or .claude/settings.local.json:
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/worktree-install.sh"
}
]
}
]
}
}
Then the script at .claude/hooks/worktree-install.sh:
# Auto-install deps when session starts in a worktree
# without node_modules
INPUT=$(cat)
CWD=$(echo "$INPUT" | python3 -c \
"import sys,json; print(json.load(sys.stdin).get('cwd',''))")
[ -z "$CWD" ] && exit 0
# Detect worktree: --git-dir and --git-common-dir
# differ in worktrees
GIT_DIR=$(git -C "$CWD" rev-parse --git-dir 2>/dev/null) \
|| exit 0
GIT_COMMON=$(git -C "$CWD" rev-parse --git-common-dir \
2>/dev/null) || exit 0
[ "$GIT_DIR" = "$GIT_COMMON" ] && exit 0
# Skip if deps already installed
[ -d "$CWD/node_modules" ] && exit 0
cd "$CWD" && npm install 2>&1
Swap npm install for pnpm install, bun install, or whatever your project uses. The rest of the script stays the same.
The key bit is the worktree detection. In a regular checkout, --git-dir and --git-common-dir point to the same place. In a worktree, they differ. That's how the script knows it's running in a worktree and not your main directory.
Trust me on this one
Once you start using worktrees and get used to the workflow, you will never go back. The isolation alone is worth it, but combined with the automatic setup from hooks and .worktreeinclude, it just works.