git worktree prune: clearing stale worktree metadata

Prune exists because deleting a directory is easier than running the right command, and git has to cope with what people actually do. Agents delete a lot of directories.

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 prune deletes the administrative records under .git/worktrees/ for worktrees whose directories are no longer on disk. You run it after removing a worktree folder with rm -rf instead of git worktree remove. It never touches a directory that still exists, never deletes a branch, and silently skips locked worktrees, so it cannot lose work. Check first with git worktree prune --dry-run --verbose. If a worktree lives on a removable disk, keep a grace period with --expire 2.weeks.ago, or lock it.

What you need to know
  • Prune only clears records for directories that are already gone. It deletes no files.
  • git worktree list flags them for you: the row ends in prunable.
  • -n -v prints exactly what it would remove, and the reason.
  • git gc already runs prune --expire 3.months.ago on your behalf.
  • Locked worktrees are skipped silently. That is what lock is for.
  • A worktree you moved needs repair, never prune.

What prune removes, and what it cannot

A linked worktree is held together by two pointers that must agree. The worktree directory has a .git file (not a directory) naming an administrative directory inside the main repository, and that administrative directory has a gitdir file naming the worktree. Prune is the command that deletes half of that pair when the other half is gone.

The two pointers, on a repository at ~/code/app with a worktree at ~/code/app-auth.
$ cat ../app-auth/.git
gitdir: /Users/you/code/app/.git/worktrees/app-auth

$ cat .git/worktrees/app-auth/gitdir
/Users/you/code/app-auth/.git

$ ls .git/worktrees/app-auth
commondir  gitdir  HEAD  index  logs  ORIG_HEAD  refs

Inside .git/worktrees/<id>/. None of this is your source code.

FileHolds
gitdirAbsolute path back to the worktree’s .git file
commondirRelative path to the shared repository (../..)
HEADWhich branch or commit this worktree has checked out
indexThis worktree’s own staging area
logs/, refs/Per-worktree reflog and per-worktree refs
lockedPresent only if locked. Plain text, containing the reason

Delete the working directory without telling git and the administrative directory stays behind, describing a worktree that is not there. That record is what prune removes, and it is the only thing prune removes.

Verified against git 2.54.0, August 2026.
$ rm -rf ../app-auth                    # the tempting shortcut

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

$ git worktree prune --dry-run --verbose
Removing worktrees/app-auth: gitdir file points to non-existent location

$ git worktree prune
$ git worktree list
/Users/you/code/app       ad7da80 [main]
A worktree is two pointers that must agree; a deleted directory and a moved directory break the pair identically and both report prunable, while a locked worktree is skipped in silence ../app-auth/.git a file, not a directory app/.git/worktrees/app-auth HEAD · index · refs · gitdir gitdir: gitdir file two pointers, and they must agree the directory is gone worktree, gone record list says: prunable git worktree prune you moved it with mv worktree, moved record list says: prunable git worktree repair it is locked locked worktree, there record prune says nothing unlock, then prune A moved worktree is reported prunable in exactly the words a deleted one uses.

Spot the stale ones before you prune

You do not have to guess. git worktree list annotates a row as prunable the moment the pointer stops resolving, and -v prints the reason on an indented line underneath.

$ git worktree list -v
/Users/you/code/app       ad7da80 [main]
/Users/you/code/app-auth  ad7da80 [feature/auth]
	prunable: gitdir file points to non-existent location

# annotate anything missing for longer than a fortnight
$ git worktree list --expire 2.weeks.ago

# stable machine-readable form; prunable becomes its own line
$ git worktree list --porcelain

The annotations you will see, and what each one means.

AnnotationMeansDo
prunableThe directory is gonegit worktree prune
lockedProtected from prune, move, and removeLeave it, or unlock first
detachedNo branch, just a commitNothing. It is legitimate
bareThe main entry is a bare repositoryNothing
No annotation, path missingYou are looking at a moved worktreegit worktree repair

Every flag

The complete option surface. There are only three.

FlagDoes
-n, --dry-runReport what it would remove, change nothing
-v, --verboseName each record removed, with the reason
--expire <time>Only prune records missing for longer than that
# see first, always
git worktree prune -n -v

# keep anything that went missing in the last fortnight
git worktree prune --expire 2.weeks.ago

# no grace period at all
git worktree prune --expire now

git gc already prunes for you

Most people never need to run this command manually, because garbage collection does it. Git’s own documentation is specific: when git gc runs, it calls git worktree prune --expire 3.months.ago. The grace period is a config value.

# the default: records survive three months of absence
git config --get gc.worktreePruneExpire

# no grace period; stale records go at the next gc
git config gc.worktreePruneExpire now

# never prune automatically (worktrees on removable media)
git config gc.worktreePruneExpire never

Locked worktrees are skipped, silently

A lock is a file named locked inside the administrative directory, containing your reason as plain text. Prune honours it, and says nothing about it, which is worth knowing before you spend ten minutes wondering why nothing happened.

$ git worktree lock --reason "on an external disk" ../app-usb
$ rm -rf ../app-usb                      # volume unplugged, say

$ git worktree prune -n -v               # prints nothing at all
$ git worktree list
/Users/you/code/app      ad7da80 [main]
/Users/you/code/app-usb  ad7da80 [feature/y] locked

$ git worktree remove ../app-usb
fatal: cannot remove a locked working tree, lock reason: on an external disk
use 'remove -f -f' to override or unlock first

$ git worktree unlock ../app-usb && git worktree prune -v
Removing worktrees/app-usb: gitdir file points to non-existent location

prune, remove, or repair

Pick by what you actually did, not by what the list looks like.

SituationCommand
I want to delete this worktreegit worktree remove <path>
It has uncommitted work and I am suregit worktree remove --force
It is locked and I am suregit worktree remove -f -f
I already deleted the directorygit worktree prune
I moved the directory with mvgit worktree repair <new-path>
I moved the whole repositorygit worktree repair from the main worktree
A worktree is on a disk that comes and goesgit worktree lock
Repair, for the moved case. Note what git prints.
$ mv ../app-auth ../app-authentication   # breaks the record’s half of the pair

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

$ git worktree repair ../app-authentication
repair: gitdir incorrect: /Users/you/code/app/.git/worktrees/app-auth/gitdir

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

Keeping an agent-heavy repository tidy

One worktree per agent session means a repository that used to accumulate two or three of these now accumulates twenty. The cleanup is not hard, but it has to be automatic or it will not happen.

01

Prefer remove over rm -rf, once

Every stale record on this page exists because somebody deleted a directory. Put the right command within reach.

In your shell rc.
wtrm() {
  git worktree remove "$1" || return 1
  [ -n "${2:-}" ] && git branch -d "$2"
  return 0
}

wtrm ../app-auth feature/auth
02

Shorten the automatic grace period

git config gc.worktreePruneExpire 2.weeks.ago

Three months of phantom rows is what makes people distrust git worktree list in the first place.

03

Sweep merged worktrees on a schedule

Handles paths with spaces, which the awk one-liners everyone copies do not.
#!/usr/bin/env bash
# wt-sweep - remove worktrees whose branch is already in main, then prune.
set -uo pipefail

path=""
while IFS= read -r line; do
  case "$line" in
    "worktree "*)          path="${line#worktree }" ;;
    "branch refs/heads/"*) branch="${line#branch refs/heads/}"
      [ "$branch" = "main" ] && continue
      [ -d "$path" ] || continue
      if git merge-base --is-ancestor "$branch" main 2>/dev/null; then
        git worktree remove "$path" && git branch -d "$branch"
      fi
      ;;
  esac
done < <(git worktree list --porcelain)

git worktree prune -v

Two guards do the work. [ -d "$path" ] skips a worktree that is already gone, so one stale entry cannot abort the sweep and leave the rest untouched. And reading whole lines with IFS= read -r rather than splitting on whitespace is what keeps it correct on a repository whose path contains a space, which the awk '{print $2}' recipe circulating everywhere silently truncates.

Questions people ask

It deletes the administrative records under .git/worktrees/ for worktrees whose directories no longer exist. It is what you run after deleting a worktree directory by hand instead of using git worktree remove.

Yes. It removes no files and no branches, only records whose directory has already gone. If the directory still exists, prune leaves it entirely alone whatever state it is in.

Run git worktree prune --dry-run --verbose. It prints a line per record, such as "Removing worktrees/app-auth: gitdir file points to non-existent location", and changes nothing.

git worktree list marks them with a prunable annotation at the end of the row. Add -v to print the reason on an indented line underneath.

Yes. When git gc runs it calls git worktree prune --expire 3.months.ago. Change the grace period with the gc.worktreePruneExpire config, using now to disable it or never to suppress automatic pruning entirely.

Either the directory still exists, in which case you want remove, or the worktree is locked. Prune skips locked worktrees silently, so unlock it first with git worktree unlock.

No. Run git worktree repair with the new path. A moved worktree is reported in exactly the same words as a deleted one, and pruning it discards a live checkout that still has your work in it.

remove deletes the directory and its record together, and refuses when the worktree is dirty or locked. prune only clears records whose directory has already been removed some other way.

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. git-config: gc.worktreePruneExpire
  3. Claude Code: run parallel sessions with worktrees
Try it

No stale
records.

Continuum removes worktrees properly on session end, warning first if an agent left work behind.

free app · your subscriptions · local-first