Git Worktrees and Claude Code: A Layered Toolkit
If you use Claude Code, you’ve probably wanted to run more than one instance at a time. Maybe you’re implementing a feature in one session while fixing a bug in another, or reviewing a PR while working on something new. But two Claude instances in the same working directory will step on each other’s files and create conflicting changes. Using git worktrees solves this problem, giving each branch its own directory, all backed by the same repository, so multiple Claude sessions can run simultaneously without interference.
Until recently, using worktrees with Claude Code meant writing your own wrapper script – creating the worktree with git worktree add, cd-ing into it, and launching claude from there. It worked, but it was manual and easy to get wrong (forgetting to branch, creating the worktree in the wrong place, leaving orphaned worktrees behind). Now Claude Code has built-in worktree support, and it handles the setup for you.
I’ve found that the best workflow involves three layers of tooling, each building on the last: the built-in --worktree option, telling Claude how to finish the branch, and a shell function for navigating between worktrees. A typical session looks like this:
- Start isolated:
claude --worktree your-branch-nameto create a fresh worktree and branch - Implement: Work with Claude as usual – the worktree keeps your main checkout clean
- Test outside Claude: Open a separate terminal in the worktree (
gw– described below), run a dev server, or manually verify behavior - Finish the branch: Ask Claude to create a PR, merge locally, or discard
- Move on:
gw mainto switch back, or start anotherclaude --worktreefor the next task
You can adopt as many layers as you want. Let’s look at each one.
Layer 1: claude --worktree
The simplest way to start is the --worktree option:
1claude --worktree your-branch-name
This creates a new git worktree under .claude/worktrees/ with a fresh branch based on HEAD (creating it if it doesn’t already exist), then starts Claude Code inside it. Your main working directory stays untouched. When the session ends normally, Claude prompts you to keep or remove the worktree (if the process is killed or the terminal closes unexpectedly, the worktree is kept and you can resume it later).
It requires no configuration or setup, making it a good starting point if you’re not yet using worktrees with Claude Code.
Because each worktree is isolated, Claude can install dependencies, modify files, and run tests without affecting your main checkout. If the feature doesn’t work out, you remove the worktree and nothing has changed.
The tradeoff is that each worktree needs its own dependency installation – node_modules, virtual environments, etc. aren’t shared. In practice this isn’t a big deal if your package manager is fast and maintains a cache (e.g., uv for Python, pnpm for Node, or cargo for Rust), but it’s worth knowing about if your project has a slow install step.
The number of concurrent sessions you can run is limited only by your Claude subscription tier and how quickly you can manage the responses. Keep in mind that multiple sessions running tests or build processes simultaneously can be resource intensive on your machine. I’ve comfortably run four simultaneous sessions on an M1 Mac Mini with a Claude Max 5x subscription plan.
Layer 2: Finishing a Development Branch
The --worktree option handles creation well, but once implementation is done you still need to decide how to integrate the work. Do you merge locally? Push and create a PR? Keep the branch for later? Discard it?
The simplest approach is to just tell Claude what you want. “Push this branch and create a PR” or “merge this into main” both work. Claude has access to git and gh, so it can push, open PRs with a description, or merge locally without you leaving the session.
For PRs, Claude will push the branch and use gh pr create – you get a link back and the worktree stays alive in case you need to address review comments. For local merges, Claude handles pulling the latest base branch, merging it into the feature branch (or rebasing), and cleaning up the worktree once you’re done. If the feature didn’t work out, just exit the session – Claude will ask if you want to remove the branch and worktree on the way out.
One thing to watch for: Claude occasionally needs a nudge to get the sequence right. For example, it might try to create a PR before pushing the branch, or skip pulling the latest changes before a local merge. A quick “push first, then create the PR” or “pull main before merging” is usually enough. These are easy to catch – Claude will usually notice the error and correct itself – but it’s worth paying attention the first few times you use this workflow.
This turns the end of a feature from a series of manual git commands into a single decision point, which I find especially useful for the merge case – it’s a sequence I’d otherwise have to remember and execute by hand.
Layer 3: Navigating Between Worktrees with gw
Claude runs inside the worktree, but sometimes you need to check things out for yourself. Maybe you want to start a dev server to test Claude’s changes in the browser. Maybe you need to run a performance benchmark that isn’t part of the standard test suite. Maybe you just want to poke around and see what Claude built before deciding whether to merge.
I wrote a shell function called gw for this. It lists all worktrees for the current repo and lets you switch between them:
1# Interactive selection with fzf
2gw
3
4# Jump directly to a worktree by branch name
5gw feature-auth
The function uses git worktree list to enumerate worktrees, parses out the branch names and paths, and either presents them via fzf for interactive selection or matches a branch name you pass as an argument. It pre-selects your current worktree in the fzf list so you can see where you are.
Here’s the full source – add it to your .bashrc/.zshrc:
Click to expand gw function
1gw() {
2 if [[ "$1" == "--help" || "$1" == "-h" ]]; then
3 cat <<'EOF'
4Usage: gw [branch-name]
5
6Switch to a git worktree directory.
7
8 gw Interactive selection via fzf (pre-selects current worktree)
9 gw <branch> Switch directly to the worktree for <branch>
10 gw -h|--help Show this help message
11EOF
12 return 0
13 fi
14
15 local worktrees
16 worktrees=$(git worktree list --porcelain | awk '
17 /^worktree / { path = substr($0, 10) }
18 /^branch / { branch = $NF; sub(/.*\//, "", branch); print branch "\t" path }
19 ' | sort -f)
20
21 if [ -n "$1" ]; then
22 local dir
23 dir=$(echo "$worktrees" | awk -F'\t' -v b="$1" '$1 == b { print $2; exit }')
24 if [ -n "$dir" ]; then cd "$dir"; else echo "No worktree matching branch '$1'"; fi
25 else
26 if ! command -v fzf &>/dev/null; then
27 echo "fzf is required for interactive selection. Install it or pass a branch name." >&2
28 return 1
29 fi
30 local selected
31 local toplevel
32 toplevel=$(git rev-parse --show-toplevel 2>/dev/null)
33 local fzf_pos=""
34 if [ -n "$toplevel" ]; then
35 # Find the line number of the worktree matching the current git toplevel
36 local pos
37 pos=$(echo "$worktrees" | awk -F'\t' -v dir="$toplevel" '$2 == dir { print NR }')
38 [ -n "$pos" ] && fzf_pos="--bind=load:pos($pos)"
39 fi
40 selected=$(echo "$worktrees" | fzf --no-sort --layout=reverse --with-nth=1 --preview 'echo {2}' --preview-window=up:1 $fzf_pos)
41 [ -n "$selected" ] && cd "$(echo "$selected" | cut -f2)"
42 fi
43}
Putting It All Together
Each layer is independent, so you can start with --worktree and add the others as they become useful. The shell function is optional – it just removes friction from the parts of the workflow that --worktree doesn’t cover.
If you close a Claude session but keep the worktree, you can pick up where you left off. Navigate to the worktree directory (e.g., gw your-branch-name) and run claude from there – it will start a new session in the existing worktree without creating a new one. Your files and branch are exactly as you left them.
What I like about this setup is that it turns Claude Code from a single-threaded tool into something closer to a small team – each worktree is an independent workstream, and you can context-switch between them as needed. The overhead is minimal once you have the shell function in place, and the isolation means you never have to worry about one session’s changes colliding with another’s.
What’s Next
I’m working on a Claude Code skill (a reusable prompt workflow) to automate closing a worktree – handling the merge-or-discard decision, branch cleanup, and worktree removal in a single step. Follow me on Bluesky or LinkedIn to be the first to know when it’s available.