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.
- Prune only clears records for directories that are already gone. It deletes no files.
git worktree listflags them for you: the row ends inprunable.-n -vprints exactly what it would remove, and the reason.git gcalready runsprune --expire 3.months.agoon your behalf.- Locked worktrees are skipped silently. That is what
lockis for. - A worktree you moved needs
repair, neverprune.
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.
$ 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.
| File | Holds |
|---|---|
gitdir | Absolute path back to the worktree’s .git file |
commondir | Relative path to the shared repository (../..) |
HEAD | Which branch or commit this worktree has checked out |
index | This worktree’s own staging area |
logs/, refs/ | Per-worktree reflog and per-worktree refs |
locked | Present 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.
$ 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]
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.
| Annotation | Means | Do |
|---|---|---|
prunable | The directory is gone | git worktree prune |
locked | Protected from prune, move, and remove | Leave it, or unlock first |
detached | No branch, just a commit | Nothing. It is legitimate |
bare | The main entry is a bare repository | Nothing |
| No annotation, path missing | You are looking at a moved worktree | git worktree repair |
Every flag
The complete option surface. There are only three.
| Flag | Does |
|---|---|
-n, --dry-run | Report what it would remove, change nothing |
-v, --verbose | Name 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.
| Situation | Command |
|---|---|
| I want to delete this worktree | git worktree remove <path> |
| It has uncommitted work and I am sure | git worktree remove --force |
| It is locked and I am sure | git worktree remove -f -f |
| I already deleted the directory | git worktree prune |
I moved the directory with mv | git worktree repair <new-path> |
| I moved the whole repository | git worktree repair from the main worktree |
| A worktree is on a disk that comes and goes | git worktree lock |
$ 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.
Prefer remove over rm -rf, once
Every stale record on this page exists because somebody deleted a directory. Put the right command within reach.
wtrm() {
git worktree remove "$1" || return 1
[ -n "${2:-}" ] && git branch -d "$2"
return 0
}
wtrm ../app-auth feature/auth
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.
Sweep merged worktrees on a schedule
#!/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.