Git worktrees let you check out multiple branches at the same time, each in its own folder, all sharing one
.gitdirectory. And with Claude Code's--worktreeflag, you can run multiple AI coding sessions at once without file conflicts. This guide covers everything from first setup to parallel AI workflows.
You're working on a feature. You have uncommitted changes. A production bug comes in and needs a fix now.
With standard Git, you have three bad options:
-
git stasheverything, switch branches, fix the bug,git stash pop, get back to where you were - Clone the repo a second time and manage two out-of-sync copies
- Make a half-finished WIP commit just so you can switch branches
Git worktrees remove all three of these options by letting you work on multiple branches at the same time, each in its own folder.
What Are Git Worktrees?
A worktree is a folder with its own branch checked out. It shares the same .git history as your main project, but has its own working files.
A normal clone gives you one folder, one branch at a time:
~/my-project/
.git/ ← history lives here
src/
package.json ← whatever branch you have checked out
With worktrees, you get multiple folders, each on its own branch:
~/my-project/
.git/ ← one shared history
src/
package.json ← main branch
~/my-project-auth/
src/
package.json ← feature/auth branch
~/my-project-hotfix/
src/
package.json ← hotfix/payment-crash branch
You can edit files in all three folders at the same time. Changes in one folder don't touch the others. And since they share one .git, a git fetch in any folder makes new remote branches visible everywhere.
What's shared vs. what's separate
| Shared across all worktrees | Separate per worktree |
|---|---|
| Commit history | Checked-out branch |
| Remote refs (after a fetch) | Staged changes |
| Hooks | Unstaged changes |
| Local Git config | Working directory files |
| Local branches | Shell $PWD
|
| Tags |
MERGE_HEAD, CHERRY_PICK_HEAD
|
Worktrees vs. Multiple Clones
Some people solve the "two branches at once" problem by cloning the repo twice. Here's why that's worse:
Storage. Every clone copies the full .git object store. A 500 MB repo becomes 1 GB across two clones. Worktrees only add the weight of the working files — usually a few megabytes.
No shared state. git fetch in Clone A does nothing for Clone B. A local branch you create in Clone A doesn't exist in Clone B until you push it. You're managing two separate databases by hand.
Hooks only exist in one place. Git hooks (pre-commit linting, push guards, etc.) live in .git/hooks. Clone B starts with nothing. With worktrees, all hooks apply everywhere because there's one .git.
Git protects you. Git won't let you check out the same branch in two worktrees at once. With two clones, nothing stops both from modifying the same branch simultaneously. You won't notice until you push.
Basic Setup: Your First Worktree
Start from a normal clone. These commands work from there.
New worktree, new branch
# Creates ../feature-auth directory on a new branch named feature/auth
git worktree add ../feature-auth -b feature/auth
The path and the branch name are independent. Use -b to name them separately.
New worktree, auto-named branch
# Branch name comes from the last part of the path: "feature-auth"
git worktree add ../feature-auth
Git names the branch after the last path segment. Fine for quick tasks. Use -b when the branch name matters.
Worktree from an existing branch
# Check out a branch that already exists
git worktree add ../bugfix-session bugfix/payment-crash
Worktree from a remote branch
# Create a local branch that tracks the remote one
git worktree add -b feature/dark-mode ../dark-mode origin/feature/dark-mode
Use this to review a colleague's branch without touching your own work.
Then work normally
cd ../feature-auth
npm install # each worktree needs its own node_modules
npm run dev
Each worktree is a fresh checkout. Dependencies, virtual environments, and build output are not shared between worktrees. Install them in each one.
List your worktrees
git worktree list
/home/you/my-project abc1234 [main]
/home/you/feature-auth def5678 [feature/auth]
/home/you/bugfix-session ghi9012 [bugfix/payment-crash]
Remove a worktree
git worktree remove ../feature-auth
This removes the folder and the Git bookkeeping. The branch stays unless you delete it with git branch -d feature/auth.
The Bare Clone Setup
The basic setup works, but the folder layout is awkward. Your main checkout sits alongside its own worktrees:
~/my-project/ ← main branch (also has working files)
~/feature-auth/ ← worktree
~/bugfix/ ← worktree
The main branch is in a special position even though it shouldn't be. Worktrees should all be equal.
The bare clone setup fixes this. Instead of a normal clone, you clone only the history — no working files — and create every branch as a worktree, including main.
How to set it up
# 1. Create a clean project folder
mkdir ~/Projects/my-project
cd ~/Projects/my-project
# 2. Clone history only, no working files
git clone --bare git@github.com:user/repo.git .bare
# 3. Tell Git where the history is
echo "gitdir: ./.bare" > .git
# 4. Fix remote tracking — required, see explanation below
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
# 5. Fetch all remote branches
git fetch --all
# 6. Create your first worktree
git worktree add main
Your folder now looks like this:
~/Projects/my-project/
.bare/ ← git history
.git ← one-line file pointing to .bare
main/ ← worktree for main branch
Add more as needed:
git worktree add feature/auth
git worktree add hotfix/payment-crash
~/Projects/my-project/
.bare/
.git
main/
feature/
auth/
hotfix/
payment-crash/
Every branch is a folder. When work is done and merged, remove the folder.
The "blind clone" problem
After a bare clone, run git fetch and you might see only:
* branch HEAD -> FETCH_HEAD
That means Git only knows about the default branch. All other remote branches are invisible.
This happens because bare clones are designed as server mirrors, not dev environments. They don't set up remote tracking by default.
Fix it:
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch --all
The first line tells Git to map every remote branch to a local tracking reference. After that, git fetch --all shows everything. Run these two lines every time you do a bare clone.
If you skip this step, git worktree add will silently create a new empty branch instead of checking out the remote branch you wanted.
Automation script
Save this as wtree in your ~/.local/bin/ and run chmod +x wtree:
#!/bin/bash
# Usage: wtree <git-url>
# Run this in an empty folder.
REPO_URL=$1
SCRIPT_NAME=$(basename "$0")
if [ -z "$REPO_URL" ]; then
echo "Usage: $0 <repo-url>"
exit 1
fi
FILES=$(ls -A | grep -v "$SCRIPT_NAME")
if [ -n "$FILES" ]; then
echo "Error: folder is not empty."
exit 1
fi
git clone --bare "$REPO_URL" .bare
echo "gitdir: ./.bare" > .git
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch --all
echo "Done. Run: git worktree add main"
Command Reference
git worktree add
# New branch, explicit name
git worktree add <path> -b <branch>
# New branch, name comes from path
git worktree add <path>
# Existing local branch
git worktree add <path> <existing-branch>
# From a remote branch
git worktree add -b <local-name> <path> <remote>/<branch>
git worktree list
git worktree list
# Machine-readable output
git worktree list --porcelain
git worktree remove
# Remove a worktree (fails if there are uncommitted changes)
git worktree remove <path>
# Force removal, discards uncommitted changes
git worktree remove --force <path>
git worktree prune
If you delete a worktree folder with rm -rf instead of git worktree remove, Git keeps stale records pointing to the now-missing folder. prune cleans those up.
# Remove stale records
git worktree prune
# Preview what would be removed
git worktree prune --dry-run
git worktree lock / unlock
Prevent prune from removing a worktree — useful for worktrees on external drives:
git worktree lock <path> --reason "On external drive"
git worktree unlock <path>
git worktree move
Move a worktree to a new path:
git worktree move ../feature-auth ../workspaces/feature-auth
git worktree repair
If you moved the .git directory or the worktree folder manually, Git loses track of it. Run this from inside the worktree to re-link it:
git worktree repair
Real-World Workflows
Hotfix while in the middle of a feature
# You're in ~/project/feature/redesign with uncommitted work.
# A bug needs fixing now. Don't stash, don't stop.
# From the project root, add a worktree for the fix
git worktree add hotfix/payment-crash
# Open a second terminal
cd ~/project/hotfix/payment-crash
# Fix the bug
git add .
git commit -m "fix: null check on payment processor callback"
git push origin hotfix/payment-crash
# Your first terminal still has the redesign exactly where you left it
Compare two implementations side by side
git worktree add approach-a -b experiment/approach-a
git worktree add approach-b -b experiment/approach-b
# Two terminals, two dev servers
cd approach-a && npm run dev -- --port 3001
cd approach-b && npm run dev -- --port 3002
# Open both in the browser and compare
Review a PR without losing your place
git fetch origin pull/1234/head:pr-1234
git worktree add ../pr-1234 pr-1234
cd ../pr-1234
# Review, run tests, check behavior
# When done
cd ..
git worktree remove pr-1234
git branch -d pr-1234
Long-running parallel features
git worktree add feature/payments -b feature/payments
git worktree add feature/notifications -b feature/notifications
# Work on payments in one terminal, notifications in another.
# git fetch in either one updates both.
Shell Aliases and Shortcuts
Git aliases
git config --global alias.wta '!f() { git worktree add -b "$1" "../$1"; }; f'
git config --global alias.wtr '!f() { git worktree remove "../$1"; }; f'
git config --global alias.wtl 'worktree list'
Usage:
git wta feature-auth # creates ../feature-auth on branch feature-auth
git wtr feature-auth # removes it
git wtl # lists all worktrees
PR checkout function
Add to your ~/.bashrc or ~/.zshrc. Requires the GitHub CLI:
# Usage: cpr <PR_NUMBER>
cpr() {
local pr="$1"
local remote="${2:-origin}"
local branch
branch=$(gh pr view "$pr" --json headRefName -q .headRefName)
git fetch "$remote" "$branch"
git worktree add "../$branch" "$branch"
cd "../$branch" || return
echo "Ready: PR #$pr ($branch)"
}
Run cpr 1234 and you're in a folder with that PR checked out.
Fuzzy worktree switcher
Requires fzf:
wts() {
local selected
selected=$(git worktree list | awk '{print $1}' | fzf --prompt="Switch to worktree: ")
[ -n "$selected" ] && cd "$selected"
}
Type wts, search by name, press Enter.
Editor Tooling
VS Code, Cursor, Windsurf
Install the Git Worktree extension by PhilStainer. It adds worktree commands to the Command Palette:
- List and jump between worktrees
- Add and remove worktrees from the GUI
Each worktree opens as a separate VS Code window since each is a different folder on disk. Treat each one as its own project.
Pairs well with the Project Manager extension if you switch between many worktrees often.
Neovim / Vim
Session managers like vim-obsession or persistence.nvim save a session per directory automatically, so each worktree gets its own saved state.
JetBrains IDEs
Open each worktree as a separate project window. The IDE detects the shared .git and shows branch info correctly.
Claude Code + Worktrees
Without worktrees, two Claude Code sessions in the same directory will overwrite each other's files. The second session has no idea what the first wrote. You get silent conflicts.
Worktrees give each session its own directory and branch. They can run at the same time without touching each other's work.
Claude Code has this built in with the --worktree flag.
Start a session in a worktree
claude --worktree feature-auth
# short form:
claude -w bugfix-payment
Claude creates .claude/worktrees/feature-auth/, checks out a new branch named worktree-feature-auth, and starts the session there.
Open another terminal and run a second session:
claude -w bugfix-payment
Both run at the same time. Neither can see the other's files.
First-time setup: Before using --worktree in a repo, run claude once in that directory to accept the workspace trust prompt. After that, the flag works without extra steps.
Let Claude pick a name
claude --worktree
# Creates something like: .claude/worktrees/bright-running-fox/
Check out a pull request
claude --worktree "#1234"
# Creates .claude/worktrees/pr-1234/ from that PR's branch
Claude fetches the branch and starts the session in it.
Copy .env files into every worktree
Worktrees are fresh checkouts. Files like .env and .env.local won't be there unless you copy them.
Create a .worktreeinclude file in the project root. Same syntax as .gitignore. Only files that match AND are already gitignored will be copied:
# .worktreeinclude
.env
.env.local
config/secrets.json
This applies to every worktree Claude creates — via --worktree, via subagents, or via the desktop app.
Also add this to .gitignore so worktree contents don't show up as untracked files:
.claude/worktrees/
Isolate subagents
When Claude delegates work to subagents, multiple subagents can write to the same files at once — same conflict problem. Tell Claude to isolate them:
"Use worktrees for your agents."
Or set it permanently in a custom subagent's frontmatter:
---
name: backend-agent
isolation: worktree
---
Each subagent gets its own worktree. When the subagent finishes with no changes, the worktree is removed automatically.
Base branch: remote vs. local HEAD
By default, new worktrees branch from origin/HEAD — the latest remote state. To branch from your current local HEAD instead (including unpushed commits), add this to your Claude Code settings:
{
"worktree": {
"baseRef": "head"
}
}
Use "head" when subagents need to work on code you haven't pushed yet. Use the default when you want each session to start from a clean remote state.
What happens when a session ends
| Session state on exit | What Claude does |
|---|---|
| No commits, no changes | Removes the worktree and branch automatically |
| Has commits or uncommitted changes | Asks: keep or remove? |
Non-interactive mode (-p flag) |
No cleanup — remove manually |
To clean up manually:
git worktree list
git worktree remove .claude/worktrees/feature-auth
Desktop app
The Claude Code desktop app creates a worktree for every new parallel session automatically. No flags needed. .worktreeinclude still applies.
Three sessions at once: a full example
# .gitignore
echo ".claude/worktrees/" >> .gitignore
# .worktreeinclude
echo ".env" >> .worktreeinclude
# Three terminals
claude -w add-oauth-login
claude -w fix-rate-limiter
claude -w migrate-to-drizzle
When each finishes:
# Review the work
git diff main..worktree-add-oauth-login
git diff main..worktree-fix-rate-limiter
git diff main..worktree-migrate-to-drizzle
# Merge what's good
git merge worktree-fix-rate-limiter
Common Pitfalls
Deleting a folder with rm -rf instead of git worktree remove
Git keeps records pointing to the deleted folder. The next time you try to add a worktree at the same path:
fatal: 'feature-auth' is already checked out at '...'
Fix:
git worktree prune
git worktree add <path> <branch>
You can't check out the same branch in two worktrees
Git blocks this. If you need two sessions on the same branch, create a new branch from it:
git worktree add ../review-copy -b review/feature-auth feature/auth
Dependencies aren't shared
node_modules, Python virtual environments, compiled binaries — each worktree needs its own. After creating a worktree:
cd ../new-worktree
npm install # or pip install, cargo build, etc.
Bare clone without the fetch fix
After git clone --bare, always run:
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch --all
Skip this and you can only see the default branch. Everything else on the remote is invisible.
Running many sessions in parallel is resource-heavy
Multiple Claude sessions each running tests, compilation, and a dev server adds up. Four parallel sessions runs fine on an M-series Mac with 16 GB RAM. Adjust based on your hardware.
claude --worktree fails with a workspace trust error
Run claude once in the project folder to accept the trust prompt. Then --worktree works:
claude # accept the prompt, then Ctrl+C
claude -w my-task # now works
Cheat Sheet
Git commands
# Create worktree (new branch)
git worktree add <path> -b <branch>
# Create worktree (existing branch)
git worktree add <path> <existing-branch>
# Create worktree (from remote branch)
git worktree add -b <name> <path> <remote>/<branch>
# List all worktrees
git worktree list
# Remove a worktree
git worktree remove <path>
# Clean up stale worktree records
git worktree prune
# Lock a worktree (protect from prune)
git worktree lock <path>
# Move a worktree
git worktree move <old-path> <new-path>
Git aliases
git config --global alias.wta '!f() { git worktree add -b "$1" "../$1"; }; f'
git config --global alias.wtr '!f() { git worktree remove "../$1"; }; f'
git config --global alias.wtl 'worktree list'
Bare clone setup
mkdir ~/Projects/my-project && cd ~/Projects/my-project
git clone --bare <repo-url> .bare
echo "gitdir: ./.bare" > .git
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch --all
git worktree add main
Claude Code
# Named worktree
claude --worktree <name>
claude -w <name>
# Auto-named
claude --worktree
# From a pull request
claude --worktree "#<pr-number>"
# settings.json — branch from local HEAD
{ "worktree": { "baseRef": "head" } }
Files to create in your project
# .gitignore
echo ".claude/worktrees/" >> .gitignore
# .worktreeinclude
echo ".env" >> .worktreeinclude
echo ".env.local" >> .worktreeinclude
Summary
Worktrees change one thing: instead of having one branch checked out at a time, every branch is a folder. Switching context means opening a different terminal, not stashing and hoping you remember what you were doing.
The bare clone setup takes five minutes to learn and gives you a cleaner structure for repos where you work across many branches regularly.
If you use Claude Code, the --worktree flag is the practical way to run multiple sessions without them stepping on each other. Set up .worktreeinclude once, add .claude/worktrees/ to .gitignore, and you're done.
Top comments (0)