git worktree add creates a second working directory backed by the same .git object store. You get real isolation of files and branch state with almost no disk cost, no second clone, and no re-fetching. Everything under refs/ is shared except refs/bisect, refs/worktree, and refs/rewritten; HEAD, the index, and the working files are private per worktree. The friction is entirely in untracked files: node_modules, .env, and build caches do not come along.
- A worktree is a second checkout of one repository, not a second copy of it. History, remotes, and objects are shared.
git worktree add ../dir branchis the whole core of it.- Git refuses to check out the same branch twice. That is a feature, and it is the error people hit first.
- Untracked and gitignored files do not come with you. Dependencies and env files need a plan.
- The stash is shared across worktrees. So is
git config, unless you turn onextensions.worktreeConfig. - Clean up with
git worktree remove; rungit worktree pruneafter deleting a directory by hand, andgit worktree repairafter moving one.
What a worktree actually is
A normal clone has one working directory and one .git directory holding the object database, refs, and config. A worktree adds a second working directory that points back at that same .git.
In the new directory, .git is not a directory at all. It is a plain text file containing a pointer.
$ cat ../myapp-feature/.git
gitdir: /Users/you/code/myapp/.git/worktrees/myapp-feature
# Inside that worktree, git resolves two different roots:
$ git rev-parse --git-dir
/Users/you/code/myapp/.git/worktrees/myapp-feature
$ git rev-parse --git-common-dir
/Users/you/code/myapp/.git
That indirection is the entire trick. $GIT_DIR is private to the worktree; $GIT_COMMON_DIR is the shared repository. Both directories read and write the same objects, remotes, and branch list. What they do not share is which branch is checked out and what is in the working tree and index.
| Shared across worktrees | Private to each worktree |
|---|---|
| Commit and object database | Checked-out branch or commit |
Everything under refs/: branches, tags, remotes | Working directory files |
git config at repo level (by default) | Index and staged changes |
| Stash (one shared stash) | Untracked and ignored files |
| Hooks | HEAD, MERGE_HEAD, in-progress operations |
| Reflog for shared refs | refs/bisect, refs/worktree, refs/rewritten |
Those three per-worktree ref namespaces are the reason a bisect in one worktree does not corrupt a rebase in another. You can read another worktree's HEAD deliberately if you need to, via git rev-parse main-worktree/HEAD or worktrees/foo/HEAD. Never poke at $GIT_DIR by hand to do it.
Why not just clone again?
A second clone works but pays for it. It re-downloads the whole history, doubles disk use, gets its own remotes to keep in sync, and gives you two separate sets of local branches that drift apart. A worktree costs one working copy of the files and nothing else. On a large repo the difference is minutes and gigabytes.
The full command surface
There are eight subcommands. Most people use three and are then surprised by the other five when something goes wrong.
git worktree subcommands, from the git-worktree(1) manual.
| Subcommand | What it does | The flag that matters |
|---|---|---|
add | Create a linked worktree | -b, -B, --detach, --orphan, --no-checkout, --track, --lock |
list | Show every worktree | --porcelain, -z, -v |
remove | Delete a worktree and deregister it | -f (twice for a locked one) |
prune | Drop stale registrations | -n, -v, --expire |
lock / unlock | Protect a worktree from prune, move, or removal | --reason |
move | Relocate a worktree properly | -f. Cannot move the main worktree |
repair | Fix pointers after something moved | Pass every new path at once |
Create a worktree on a new branch
The common case. -b creates the branch and checks it out in the new directory in one step. Omit the commit-ish and git branches from HEAD.
git worktree add -b feature/checkout ../myapp-checkout
# From a specific base rather than HEAD
git worktree add -b hotfix/login ../myapp-login origin/main
# -B if the branch may already exist and you want it reset
git worktree add -B feature/checkout ../myapp-checkout origin/main
Create a worktree on an existing branch
Drop the -b. The branch must not already be checked out somewhere else. Omit the branch entirely and git names one after the final path component.
git worktree add ../myapp-review feature/existing
# Track a remote branch of the same basename automatically
git worktree add --guess-remote ../myapp-review feature/existing
Check out a commit without a branch
Useful for bisecting or reviewing a tag without disturbing anything.
git worktree add --detach ../myapp-v2 v2.4.1
# Or an empty worktree on an unborn branch
git worktree add --orphan -b docs-rewrite ../myapp-docs
See what exists
The human form is fine day to day. The porcelain form is what you parse in a script.
$ git worktree list
/Users/you/code/myapp a1b2c3d [main]
/Users/you/code/myapp-checkout e4f5g6h [feature/checkout]
/Users/you/code/myapp-login i7j8k9l [hotfix/login] prunable
$ git worktree list --porcelain
worktree /Users/you/code/myapp
HEAD a1b2c3d...
branch refs/heads/main
Remove one when you are done
This deletes the directory and deregisters it. It refuses if you have uncommitted changes, which is the behaviour you want.
git worktree remove ../myapp-checkout
# If you really mean it and accept losing the changes
git worktree remove --force ../myapp-checkout
# The branch still exists; delete it separately if you are done with it
git branch -d feature/checkout
Clean up after deleting a directory by hand
If you rm -rf a worktree directory, git still has a stale registration. Prune clears it.
git worktree prune
# See what it would drop first
git worktree prune --dry-run --verbose
# Only prune registrations that have been missing for a while
git worktree prune --expire 2.weeks.ago
Move or repair instead of recreating
Moving a worktree directory in Finder or Explorer breaks its pointer. move does it properly; repair fixes it afterwards if you already did the wrong thing. repair also fixes every linked worktree when the main checkout moves.
git worktree move ../myapp-checkout ~/work/myapp-checkout
# Already moved things by hand? Run this from the main checkout
git worktree repair
# Several moved at once: name them all
git worktree repair ~/work/myapp-checkout ~/work/myapp-tax
Locking
If a worktree lives on removable media or a network mount, lock it so prune does not clean it up while it is unreachable. Tooling uses this too: Claude Code locks an agent's worktree while the agent runs so a concurrent sweep cannot remove it.
git worktree lock ../myapp-usb --reason "on external drive"
git worktree unlock ../myapp-usb
Per-worktree config
By default git config is shared, which bites when you want a different core.sparseCheckout or user.email in one worktree. Turn on the extension and you get a per-worktree config file.
git config extensions.worktreeConfig true
git config --worktree user.email you@work.example
# Written to $GIT_DIR/worktrees/<id>/config.worktree, read after .git/config
The six things that go wrong
1. "branch is already checked out"
$ git worktree add ../another main
fatal: 'main' is already checked out at '/Users/you/code/myapp'
Git will not let two worktrees hold the same branch, because both would be committing to one ref and immediately diverge. Either branch off it with -b, or use --detach if you only want to read the tree.
2. Dependencies are not there
A new worktree contains tracked files only. node_modules, .venv, target, and every other ignored directory is absent, so your first command in a fresh worktree usually fails.
# 1. Just install. Correct, slow, uses disk.
cd ../myapp-checkout && npm ci
# 2. Symlink when the dependency tree is identical.
ln -s ../myapp/node_modules ../myapp-checkout/node_modules
# 3. Use a content-addressed store that shares by design.
pnpm install # hard-links from a global store, so this is fast and safe
3. Environment files are missing
.env, local certificates, and editor config are gitignored for good reason, and that means they do not follow you. Copy them in as part of creating the worktree.
wt() {
local name="$1"
local base="${2:-origin/main}"
local root
root="$(git rev-parse --show-toplevel)" || return 1
local dir="${root}-${name##*/}"
git worktree add -b "$name" "$dir" "$base" || return 1
# Carry over the ignored files a fresh checkout will not have
for f in .env .env.local .envrc; do
[ -f "$root/$f" ] && cp "$root/$f" "$dir/$f"
done
echo "ready: $dir"
}
# usage: wt feature/checkout origin/main
4. Where to put the directories
Two conventions. Siblings (../myapp-feature) keep your editor's file watcher out of trouble but scatter directories around your home folder. A nested directory (./.worktrees/feature) keeps everything together but must be gitignored, or you will commit a worktree into the repo.
echo ".worktrees/" >> .gitignore
git worktree add -b feature/x .worktrees/feature-x
5. One stash, shared
The stash is repository-level, not worktree-level. Stash in one worktree and it appears in git stash list everywhere. Popping it into the wrong worktree is a genuinely confusing five minutes. Prefer a throwaway commit (git commit -am wip) over stashing when you are running several worktrees.
6. Moving a worktree by hand
Drag a worktree directory somewhere else and its .git pointer still names the old path, so every git command in it fails. git worktree repair fixes it from the main checkout. Moving the main checkout breaks all of them at once, and the same command fixes them all.
Why AI agents changed the calculus
Worktrees have existed since git 2.5 in 2015 and most developers never needed them, because a human works on one thing at a time. Switching branches was cheap enough.
Agents broke that assumption. An agent can work for twenty minutes unattended, which means the rational move is to start a second one rather than watch the first. The moment two agents run against the same directory, they are editing each other's files mid-edit, and you get corrupted state that looks like a model failure but is actually a filesystem race.
Claude Code has this built in. Starting a session with --worktree (or -w) calls git worktree add for you, places the worktree under .claude/worktrees/<name>/ at the repository root, and puts it on a branch named worktree-<name>.
claude --worktree feature-auth # .claude/worktrees/feature-auth, branch worktree-feature-auth
claude --worktree # Claude generates a name like bright-running-fox
claude --worktree "#1234" # branch from a pull request; quote it or the shell eats the #
# Keep the worktrees out of your main checkout's status
echo ".claude/worktrees/" >> .gitignore
New worktrees branch from the repository's default branch on the remote by default. Set worktree.baseRef to "head" in settings when you want them to carry your unpushed work instead.
That covers the single-agent case well. What it does not do is give you a view across several running agents, which is where dedicated tooling comes in.
A worked example
Say a bug report lands while you are mid-feature, with uncommitted work you do not want to stash.
# You are here, with dirty files you are not ready to commit
$ git status --short
M src/checkout/cart.ts
M src/checkout/total.ts
# Make a clean room from origin/main, leaving this desk untouched
$ git worktree add -b hotfix/tax-rounding ../myapp-tax origin/main
$ cd ../myapp-tax && cp ../myapp/.env . && npm ci
# ... fix, test, push ...
$ git commit -am "fix: round tax to cents before summing"
$ git push -u origin hotfix/tax-rounding
$ gh pr create --fill
# Done. Tear the room down.
$ cd ../myapp
$ git worktree remove ../myapp-tax
$ git branch -d hotfix/tax-rounding
# Your feature work never moved
$ git status --short
M src/checkout/cart.ts
M src/checkout/total.ts
No stash, no context switch, no "which branch was I on". This is the workflow that makes worktrees worth learning even if you never run an agent.
Periodic housekeeping
git worktree list # what is still open
git worktree prune --dry-run -v # what would be dropped
git worktree prune # drop it
# Delete local branches already merged into main
git branch --merged main | grep -v '^\*\| main$' | xargs -r git branch -d
Questions people ask
A branch is a pointer to a commit. A worktree is a directory with files checked out. Normally one repository has many branches and one worktree; git worktree lets one repository have several worktrees, each with a different branch checked out.
Only one working copy of the files each. The object database, which is usually the large part of a repository, is shared. A second worktree is dramatically cheaper than a second clone and needs no re-fetch.
Both would move the same ref and immediately conflict. Use -b to branch from it, or --detach to check out the commit without claiming the branch.
No. Only tracked files are checked out. Install fresh, symlink if the dependency trees are identical, or use a package manager like pnpm that hard-links from a shared store.
git worktree remove <path>. If you already deleted the directory manually, run git worktree prune to clear the stale registration. Deleting a worktree does not delete its branch, so follow with git branch -d.
Run git worktree repair from the main checkout. Better, use git worktree move next time, which updates the pointer for you. If the main checkout is what moved, git worktree repair fixes every linked worktree at once.
Yes. The stash is repository-level, so a stash made in one worktree shows up in all of them. Prefer a work-in-progress commit when you are juggling several worktrees.
Yes, and it is the main reason worktrees became popular. Each agent gets isolated files and branch state so concurrent edits cannot collide. Claude Code has a built-in --worktree flag, and a .worktreeinclude file copies your gitignored env files into each new worktree.
Sources
Every figure above was read from these pages on August 2026. Vendors reprice without notice; if you find a stale number, tell us.
- git-worktree documentation the authoritative command reference
- Claude Code: run parallel sessions with worktrees the --worktree flag and .worktreeinclude