git worktree cheat sheet: every command on one page

The whole command surface in the order you will need it, with output copied from git 2.54 rather than paraphrased, and the recipes that took someone else an afternoon to work out.

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 has eight subcommands: add, list, lock, move, prune, remove, repair and unlock. Three of them are daily work. The other five exist to repair situations you will eventually create by moving or deleting directories by hand, or to protect a worktree that lives on storage which comes and goes.

What you need to know
  • Eight subcommands. Three are daily, five are repairs and safeguards.
  • list --porcelain is the machine-readable form. Never parse the human one.
  • prune fixes what deleting a directory by hand breaks.
  • repair fixes what moving a directory by hand breaks. They are not interchangeable.
  • lock stops prune touching a worktree on removable or network storage.
  • -v and --porcelain cannot be combined, and -z requires --porcelain.

Every command

The complete subcommand surface, git 2.54, August 2026.

CommandDoesHow often
git worktree addCreate a worktreeDaily
git worktree listShow all of them, with stateDaily
git worktree removeDelete one properlyDaily
git worktree pruneClear records for directories that are goneOccasionally
git worktree repairFix pointers after something movedAfter a mistake
git worktree moveRelocate one properlyRarely
git worktree lockProtect one from pruningRarely
git worktree unlockUndo the lockRarely
The full set, with the flags that matter.
# add ────────────────────────────────────────────────────
git worktree add -b feature/x ../app-x        # new branch (the usual)
git worktree add ../app-hot hotfix/login      # existing branch
git worktree add ../app-spike                 # branch named after the dir
git worktree add -b review ../app-r origin/pr-42
git worktree add --detach ../app-v1 v1.0.0    # read-only look
git worktree add -B feature/x ../app-x main   # reset the branch first
git worktree add --orphan -b docs ../app-docs # empty, unborn branch
git worktree add --no-checkout ../app-empty   # create, populate later
git worktree add --lock --reason "usb" ../app-usb

# list ───────────────────────────────────────────────────
git worktree list                             # human
git worktree list -v                          # + lock and prune reasons
git worktree list --porcelain                 # for scripts
git worktree list --porcelain -z              # NUL-terminated
git worktree list --expire 2.weeks.ago        # annotate old records

# remove ─────────────────────────────────────────────────
git worktree remove ../app-x
git worktree remove --force ../app-x          # even if dirty
git worktree remove -f -f ../app-usb          # even if locked

# prune ──────────────────────────────────────────────────
git worktree prune -n -v                      # see first, always
git worktree prune
git worktree prune --expire 2.weeks.ago

# move / lock / repair ───────────────────────────────────
git worktree move ../app-x ../renamed
git worktree lock ../app-usb --reason "on an external disk"
git worktree unlock ../app-usb
git worktree repair                           # from the main worktree
git worktree repair ../moved-worktree         # after moving one by hand

Five minutes, start to finish

If you have never used the command, this is the whole loop. Every line below was run against git 2.54.

01

Create a worktree on a new branch

$ cd ~/code/app
$ git worktree add -b feature/auth ../app-auth
Preparing worktree (new branch 'feature/auth')
HEAD is now at ad7da80 init

The directory is a sibling of the repository, which is the convention. A worktree inside the repository shows up as an untracked directory in git status and gets indexed twice by your editor.

02

Make it runnable

A worktree carries tracked files only, so gitignored things are absent. This is the step people forget, and the resulting failure looks like a broken worktree.

$ cd ../app-auth
$ ls node_modules
ls: node_modules: No such file or directory

$ cp ../app/.env .
$ pnpm install
03

Work, commit, push, open a pull request

Nothing here is worktree-specific. That is the point: the result rejoins your normal process instead of needing a special one.

$ git add -A && git commit -m "add JWT middleware"
$ git push -u origin feature/auth
$ gh pr create --fill
04

Check what you have open

$ git worktree list
/Users/you/code/app        ad7da80 [main]
/Users/you/code/app-auth   b91c4f2 [feature/auth]
05

Clean up

Removing the worktree does not remove the branch, so delete that separately once it is merged.

$ cd ../app
$ git worktree remove ../app-auth
$ git branch -d feature/auth

git worktree list, in detail

The command you will run most often after add, and the one with the most output modes. Plain list gives you path, commit and branch, with any interesting state as a trailing word.

$ git worktree list
/Users/you/code/app        ad7da80 [main]
/Users/you/code/app-det    ad7da80 (detached HEAD)
/Users/you/code/app-usb    ad7da80 [usb] locked
/Users/you/code/app-gone   ad7da80 [gone] prunable
/Users/you/code/app-auth   b91c4f2 [feature/auth]

What the annotations mean.

AnnotationMeansNext step
[branch]Normal, on a branchNothing
(detached HEAD)Created with --detachCommit here and the commits are unreachable by name
(bare)The bare repository itselfIt has no working files
lockedProtected from pruneunlock before removing or moving
prunableThe directory is missingprune, or repair if you moved it

-v adds the reason for each lock or prunable state on an indented line. It is the fastest way to answer "why will git not remove this".

$ git worktree list -v
/Users/you/code/app        ad7da80 [main]
/Users/you/code/app-usb    ad7da80 [usb]
	locked: on an external disk
/Users/you/code/app-gone   ad7da80 [gone]
	prunable: gitdir file points to non-existent location

The machine-readable form

Blank-line-separated stanzas of key and value. Stable across git versions.
$ git worktree list --porcelain
worktree /Users/you/code/app
HEAD ad7da806c132de9e6d724d0c6893bddb0bb4d20c
branch refs/heads/main

worktree /Users/you/code/app-det
HEAD ad7da806c132de9e6d724d0c6893bddb0bb4d20c
detached

worktree /Users/you/code/app-gone
HEAD ad7da806c132de9e6d724d0c6893bddb0bb4d20c
branch refs/heads/gone
prunable gitdir file points to non-existent location

Every key the porcelain form can emit.

KeyValuePresent when
worktreeAbsolute pathAlways, and always first in a record
HEADFull 40-character object idNot bare
branchrefs/heads/<name>On a branch
bareNo valueThe bare repository
detachedNo valueDetached HEAD
lockedReason, or no valueLocked
prunableReasonThe directory is missing

Recipes

Create one that actually runs

#!/usr/bin/env bash
# wt <branch> - a worktree you can immediately work in
set -euo pipefail
name="$1"
dir="../$(basename "$PWD")-${name//\//-}"

git worktree add -b "$name" "$dir"

for f in .env .env.local; do
  if [ -f "$f" ]; then cp "$f" "$dir/"; fi
done

cd "$dir"
if   [ -f pnpm-lock.yaml ];    then pnpm install
elif [ -f package-lock.json ]; then npm ci
elif [ -f bun.lockb ];         then bun install
fi

echo "$dir"

Jump between them

Needs fzf. Worth it once you have more than three.
wtj() {
  local dir
  dir=$(git worktree list | fzf --height 40% | awk '{print $1}') || return
  cd "$dir" || return
}

Remove every merged worktree

git worktree list --porcelain \
 | awk '/^worktree /{p=$2} /^branch /{print p, $2}' \
 | while read -r path ref; do
     b="${ref#refs/heads/}"
     [ "$b" = "main" ] && continue
     git merge-base --is-ancestor "$b" main 2>/dev/null \
       && git worktree remove "$path" && git branch -d "$b"
   done

Find the worktree that is not clean

The question you actually ask when four agents have been running.
git worktree list --porcelain \
 | awk '/^worktree /{print $2}' \
 | while read -r p; do
     n=$(git -C "$p" status --porcelain | wc -l | tr -d " ")
     [ "$n" -gt 0 ] && printf "%-40s %s changed\n" "$p" "$n"
   done

A bare repository with only worktrees

No privileged main checkout. Tidy for an agent-heavy setup, and verified end to end.
git clone --bare git@github.com:you/app.git app/.bare
cd app
echo "gitdir: ./.bare" > .git
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch origin

git worktree add main
git worktree add -b feature/x feature-x

# git worktree list
#   /Users/you/code/app/.bare       (bare)
#   /Users/you/code/app/feature-x   ad7da80 [feature/x]
#   /Users/you/code/app/main        ad7da80 [main]

Scripting against it

The default output is aligned for humans and will break your parser the first time a path is unusually long or contains a space. The porcelain form will not.

Flags that matter when a program is reading the output.

FlagWhy
--porcelainStable key-value stanzas rather than aligned columns
-zNUL terminators, so newlines in paths are safe
-n, --dry-runOn prune: report without changing anything
-v, --verboseOn prune: say why each record is going
--expire <time>Only touch records older than that
-q, --quietOn add: drop the progress lines

Questions people ask

Eight: add, list, lock, move, prune, remove, repair and unlock. Add, list and remove are the daily ones. The rest exist to repair situations created by moving or deleting directories by hand, or to protect a worktree on storage that is not always mounted.

git worktree list. Add -v to see the reason behind any locked or prunable annotation, or --porcelain for a stable machine-readable form when scripting. -v and --porcelain cannot be combined.

The registered directory is missing from disk. Usually you deleted it with rm -rf, in which case prune or remove clears the record. If you moved it with mv, the record is wrong rather than stale and git worktree repair is the right command.

remove deletes a worktree properly, directory and record together, and refuses if the tree is dirty. prune only clears records pointing at directories that no longer exist, so it can never lose work.

git worktree move <from> <to>. Using mv leaves the gitdir pointer naming the old path, git starts listing the worktree as prunable, and you need git worktree repair <new-path> to fix it.

It marks a worktree so prune leaves it alone, and blocks move and remove until you unlock it. It exists for worktrees on removable or network storage, which look exactly like deleted directories while unmounted. Always pass --reason.

Yes. Clone bare into app/.bare, write a .git file containing "gitdir: ./.bare", set the origin fetch refspec, and add a worktree per branch. It is a tidy arrangement when you routinely keep several checkouts and none of them is privileged.

Use git worktree list --porcelain, which prints blank-line-separated stanzas of key and value. Add -z for NUL terminators if a path could contain a newline. Never parse the default aligned output.

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 manual
  2. Pro Git book
Try it

The setup,
as a product.

Worktree per session, sidebar instead of fzf, diff pane instead of git diff, cleanup on close.

free app · your subscriptions · local-first