Git worktrees: the complete guide

A worktree lets you have several branches checked out at once, each in its own directory, all sharing one repository. It is a decade-old feature that suddenly matters because AI agents made "work on four things at once" a normal request.

By the Continuum team. We build a workbench that runs Claude Code, Codex, and their peers, so the model rates quoted here are the ones our own cost analytics ship with.

The short version

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.

What you need to know
  • 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 branch is 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 on extensions.worktreeConfig.
  • Clean up with git worktree remove; run git worktree prune after deleting a directory by hand, and git worktree repair after moving one.

What a worktree actually is

myapp/.gitobjects · refs · remotes · configmyapp/mainthe original clonemyapp-csv/feat/csv-exportagent 1myapp-tax/fix/tax-roundagent 2one repository · three working directories · no second clone
The whole idea in one picture. Three directories, three branches checked out at once, one shared object store. A second clone would duplicate everything on the left.

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.

The pointer that makes it work
$ 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 worktreesPrivate to each worktree
Commit and object databaseChecked-out branch or commit
Everything under refs/: branches, tags, remotesWorking directory files
git config at repo level (by default)Index and staged changes
Stash (one shared stash)Untracked and ignored files
HooksHEAD, MERGE_HEAD, in-progress operations
Reflog for shared refsrefs/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.

SubcommandWhat it doesThe flag that matters
addCreate a linked worktree-b, -B, --detach, --orphan, --no-checkout, --track, --lock
listShow every worktree--porcelain, -z, -v
removeDelete a worktree and deregister it-f (twice for a locked one)
pruneDrop stale registrations-n, -v, --expire
lock / unlockProtect a worktree from prune, move, or removal--reason
moveRelocate a worktree properly-f. Cannot move the main worktree
repairFix pointers after something movedPass every new path at once
01

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
02

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
03

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
04

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
05

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
06

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
07

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.

Opting into per-worktree configuration
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.

Three ways to deal with it, cheapest first
# 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.

A create-worktree helper worth having
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>.

Built-in worktree isolation
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.

Handling an interrupt without touching your current state
# 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

A cleanup pass worth running monthly
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.

  1. git-worktree documentation the authoritative command reference
  2. Claude Code: run parallel sessions with worktrees the --worktree flag and .worktreeinclude
Try it

Worktrees, wired
into the workbench.

Continuum spawns each agent session into its own git worktree and shows every one in a single sidebar, with diff and PR panes per session. Plain git underneath, nothing proprietary.

free app · your subscriptions · local-first