Worktree Cleanup and Management
Worktree cleanup and management
Worktrees accumulate.
A week of parallel agent sessions can leave a dozen worktrees scattered across the filesystem, each consuming disk space and cluttering git worktree list output.
Cleanup matters because stale worktree references cause confusing errors that are tedious to debug.
Removing worktrees correctly versus incorrectly makes a real difference.
Git maintains bidirectional links between the main repository and each linked worktree.
Deleting a worktree directory with rm -rf breaks these links, leaving orphaned metadata that causes confusing errors later.
Proper removal with git worktree remove
The git worktree remove command handles cleanup correctly:
# Remove a clean worktree
git worktree remove ../auth-feature
# Check what happens
git worktree list
# The worktree is gone from the list
# The branch can now be deleted or checked out elsewhereThe command removes the working directory, deletes the administrative files in .git/worktrees/<name>/, and releases the branch lock that prevented checking it out in another worktree.
Git protects against accidental data loss.
By default, git worktree remove refuses to delete worktrees containing uncommitted changes or untracked files:
$ git worktree remove ../experimental-feature
fatal: '../experimental-feature' contains modified or untracked files, use --force to delete itThe --force flag overrides this protection for worktrees with uncommitted work:
# Remove a worktree with uncommitted changes
git worktree remove --force ../experimental-featureFor locked worktrees, --force must be specified twice:
# Remove a locked worktree
git worktree remove --force --force ../locked-worktreeThe double-force requirement provides an additional barrier against accidentally removing worktrees that were deliberately protected.
The consequences of manual deletion
Deleting a worktree with rm -rf instead of git worktree remove creates problems that surface later:
# This leaves orphaned metadata
rm -rf ../old-feature
# The worktree still appears in the list
git worktree list
# /path/to/main abc1234 [main]
# /path/to/old-feature (error) # Directory gone but reference remains
# The branch appears locked
git branch -d old-feature
# error: Cannot delete branch 'old-feature' checked out at '/path/to/old-feature'The administrative files in .git/worktrees/old-feature/ still exist, causing Git to believe the worktree is active.
This blocks branch deletion and checkout operations until the stale references are cleaned up.
Pruning stale worktree references
The git worktree prune command cleans up orphaned references left by manual deletions or system crashes:
# Preview what would be pruned
git worktree prune --dry-run --verbose
# Output shows stale entries
# Removing worktrees/old-feature: gitdir file points to non-existent location
# Actually prune
git worktree prune --verboseThe --expire option adds a time threshold, pruning only worktrees that have been missing for longer than the specified duration:
# Prune worktrees missing for more than 7 days
git worktree prune --expire 7.days
# Prune worktrees missing for more than 2 weeks
git worktree prune --expire 2.weeks.agoThis expiration mechanism protects against premature cleanup. A worktree on an unmounted USB drive or temporarily disconnected network share should not be pruned just because the directory is temporarily inaccessible.
Git runs git worktree prune automatically during garbage collection, controlled by the gc.worktreePruneExpire configuration:
# Default: prune worktrees missing for 3 months
git config gc.worktreePruneExpire "3.months.ago"
# More aggressive for CI environments
git config gc.worktreePruneExpire "1.week.ago"
# Immediate cleanup (useful in scripts)
git config gc.worktreePruneExpire "now"Locking worktrees for protection
Locking prevents a worktree from being pruned, moved, or removed without explicit force:
# Lock a worktree with a reason
git worktree lock --reason "on external drive, may be unmounted" ../usb-worktree
# Check lock status
git worktree list --verbose
# /path/to/usb-worktree def5678 [feature] locked (on external drive, may be unmounted)The --reason parameter documents why the lock exists.
This information appears in git worktree list --verbose output and helps future cleanup decisions.
Locking makes sense for worktrees on USB drives or network shares that may be temporarily unavailable, for long-running experiments that should persist across cleanup cycles, and for CI environments where worktrees must survive between build stages.
Create worktrees pre-locked to avoid race conditions between creation and protection:
# Create a locked worktree for network storage
git worktree add --lock --reason "NFS share, may be unmounted" //server/share/build mainUnlock worktrees when protection is no longer needed:
git worktree unlock ../usb-worktreeRepairing broken worktrees
Git maintains bidirectional links between the main repository and linked worktrees. Moving either location without using Git commands breaks these links:
# This breaks the link
mv ../feature-worktree ../new-location
# Commands in the moved worktree fail
cd ../new-location
git status
# fatal: not a git repository (or any parent up to mount point /)The git worktree repair command fixes broken links:
# After moving a linked worktree, repair from the new location
cd /newpath/feature-worktree
git worktree repair
# Or repair from the main worktree, specifying new paths
cd /path/to/main
git worktree repair /newpath/feature-worktree /newpath/other-worktreeAfter moving the main repository, repair from the new main location:
cd /newpath/main
git worktree repairThe repair command updates the path references in both directions, restoring the connection between the main repository and linked worktrees.
Automation helpers
Shell functions reduce the friction of worktree management. This cleanup function removes a worktree and its branch in one operation:
# Add to ~/.bashrc or ~/.zshrc
wt-cleanup() {
if [[ -z "$1" ]]; then
echo "Usage: wt-cleanup <worktree-path>"
return 1
fi
local worktree="$1"
local branch=$(git -C "$worktree" rev-parse --abbrev-ref HEAD 2>/dev/null)
git worktree remove "$worktree" || {
echo "Failed to remove worktree. Use --force if uncommitted changes exist."
return 1
}
if [[ -n "$branch" && "$branch" != "HEAD" ]]; then
git branch -d "$branch" 2>/dev/null || \
echo "Branch '$branch' not fully merged. Use 'git branch -D $branch' to force delete."
fi
}A function to remove all worktrees for merged branches:
wt-cleanup-merged() {
local main_branch="${1:-main}"
git worktree list --porcelain | grep "^worktree" | cut -d' ' -f2 | while read -r wt; do
# Skip the main worktree
[[ "$wt" == "$(git rev-parse --show-toplevel)" ]] && continue
local branch=$(git -C "$wt" rev-parse --abbrev-ref HEAD 2>/dev/null)
# Check if branch is merged into main
if git merge-base --is-ancestor "$branch" "$main_branch" 2>/dev/null; then
echo "Removing merged worktree: $wt ($branch)"
git worktree remove "$wt"
git branch -d "$branch"
fi
done
}Git aliases provide shortcuts for common operations:
git config --global alias.wt "worktree list"
git config --global alias.wtp "worktree prune --dry-run --verbose"
git config --global alias.wtc "worktree prune"Weekly maintenance routine
A maintenance script for team environments:
#!/bin/bash
# worktree-maintenance.sh
echo "=== Worktree Status ==="
git worktree list
echo ""
echo "=== Stale References ==="
git worktree prune --dry-run --verbose
echo ""
echo "=== Merged Branches with Worktrees ==="
git worktree list --porcelain | grep "^worktree" | cut -d' ' -f2 | while read -r wt; do
branch=$(git -C "$wt" rev-parse --abbrev-ref HEAD 2>/dev/null)
if git merge-base --is-ancestor "$branch" main 2>/dev/null; then
echo "Merged: $wt ($branch)"
fi
done
echo ""
echo "=== Disk Usage ==="
du -sh .git/worktrees/* 2>/dev/null || echo "No worktree data"Running this script weekly catches problems before worktree sprawl becomes unmanageable. In team environments, consider adding it to CI as a scheduled job that reports on repository health.