Git, CI/CD & GitOpsXXIII · WorktreesWorktrees
Worktree cleanup and pruning — git worktree prune; stale metadata; the lock file
What you'll learn
- Run `git worktree prune` to clean up stale worktree metadata after a directory is deleted outside Git
- Distinguish between `git worktree remove` (clean) and `git worktree prune` (metadata-only)
- Use `git worktree lock` and `git worktree unlock` to protect long-running checkouts from accidental removal
- Apply the cleanup discipline that keeps the worktree inventory accurate
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The previous five lessons covered worktrees as a primitive:
what they are, how to create and remove them, how they share the
.git directory, how they interact with branches, and how they
are used in production. This lesson covers the cleanup mechanics
that keep the worktree inventory accurate over time:
git worktree prune for stale metadata, the difference between
prune and remove, and the lock / unlock commands that protect
long-running checkouts.
The cleanup problem
Worktree metadata lives in two places:
- The linked worktree directory (
$PATH/.gitfile pointing back at the metadata). - The main
.git’s.git/worktrees/$NAME/metadata directory.
The two must stay in sync. The git worktree add and
git worktree remove commands update both atomically. But a
worktree directory can be deleted outside Git — rm -rf,
a misconfigured cleanup script, a disk failure that destroys the
directory but not the .git — and Git’s metadata has no way to
notice until the next git worktree list, git worktree prune,
or some other command inspects the metadata directory.
# The classic stale-metadata scenario
git worktree list
# /home/alice/work/iac a1b2c3d [main]
# /home/alice/work/iac-feature-iam d4e5f6a [feature/iam-rotation]
# Outside Git, the worktree directory is deleted (rm -rf, disk loss, etc.)
rm -rf ~/work/iac-feature-iam
# `git worktree list` still shows the entry — Git does not know the directory is gone
git worktree list
# /home/alice/work/iac a1b2c3d [main]
# /home/alice/work/iac-feature-iam d4e5f6a [feature/iam-rotation]
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# directory no longer exists; metadata is stale
The metadata record .git/worktrees/iac-feature-iam/ is still
present; git worktree list reads it and reports the path as if
the directory were still there. Future operations that try to
use that worktree (e.g., git worktree remove ~/work/iac-feature-iam) fail because the directory does not
exist.
git worktree prune
The fix is git worktree prune. It walks the metadata
directories under .git/worktrees/, checks whether each linked
working tree path still exists, and removes the metadata records
for paths that no longer exist:
# Dry run first — show what prune would clean up
git worktree prune --dry-run --verbose
# Would remove worktree at '/home/alice/work/iac-feature-iam'
# Actually prune
git worktree prune --verbose
# Removed worktree at '/home/alice/work/iac-feature-iam'
# Verify the inventory is now accurate
git worktree list
# /home/alice/work/iac a1b2c3d [main]
The --dry-run flag is the production discipline: it shows what
prune would do without doing it, allowing a sanity check before
the actual cleanup. --verbose explains each removal. The
combination is safe to run at any time.
remove versus prune
The two cleanup commands have distinct purposes:
| Command | Removes directory? | Removes metadata? | Use case |
|---|---|---|---|
git worktree remove $PATH | Yes | Yes | Routine cleanup of a worktree the engineer is finished with |
git worktree remove --force $PATH | Yes (with possible uncommitted data loss) | Yes | Cleanup when the worktree has uncommitted changes that are known to be disposable |
git worktree prune | No | Yes | Cleanup of metadata after the directory was deleted outside Git |
The operational rule: use git worktree remove for worktrees
you finished; use git worktree prune for worktrees that were
deleted out from under Git. Mixing the two (running prune
when the directory still exists) is a no-op; running remove
when the directory is already gone fails with “no such
worktree”.
flowchart LR
NORMAL["normal cleanup"] -->|directory + metadata| REMOVE["git worktree remove"]
EXTERNAL["directory deleted outside Git"] -->|metadata only| PRUNE["git worktree prune"]
REMOVE --> CHECK["git worktree list verifies"]
PRUNE --> CHECK
Locking worktrees: git worktree lock
A worktree that backs a long-running operation (a Terraform
rollout, an incident investigation, a multi-day migration) needs
protection against accidental removal. The protection is
git worktree lock:
# Lock a worktree against prune and remove
git worktree lock --reason "Terraform rollout RUNBOOK-4711, ETA 36h" ~/work/iac-rollout
# Verify the lock
git worktree list --porcelain | grep -A 2 "iac-rollout"
# /home/alice/work/iac-rollout
# HEAD 8a3f9d2
# locked: --reason Terraform rollout RUNBOOK-4711, ETA 36h
A locked worktree:
- Cannot be removed by
git worktree remove(the command refuses with an error pointing at the lock). - Cannot be pruned by
git worktree prune(the prune skips locked entries). - Survives a
rm -rfof the directory — but if the directory is deleted outside Git, the lock does not protect the metadata;git worktree prunewill eventually clean up the metadata unless--no-prune-locksis set.
The lock is a single file inside the metadata directory:
.git/worktrees/$NAME/locked. The file contains the reason
text. Presence of the file is the lock; absence is unlocked.
# Inspect the lock file directly
cat ~/work/iac/.git/worktrees/iac-rollout/locked
# --reason Terraform rollout RUNBOOK-4711, ETA 36h
# Unlock when the operation is done
git worktree unlock ~/work/iac-rollout
# Verify
ls ~/work/iac/.git/worktrees/iac-rollout/
# HEAD
# commondir
# gitdir
# (no `locked` file)
When prune runs automatically
Git runs git worktree prune automatically in several places:
- After
git worktree add(it cleans up any stale metadata from previous failed adds). - After
git worktree remove(it cleans up any metadata for worktrees that have been completely removed). - During
git worktree move(it cleans up the old metadata record after the move).
Manual git worktree prune is needed when the directory was
deleted outside Git and the inventory has gone stale. The
operational discipline is to run git worktree prune --verbose
after any manual cleanup of worktree directories (e.g., a CI
runner that uses rm -rf for failed jobs), and to include
git worktree prune in any scripted worktree-cleanup
operation as a backstop.
The cleanup discipline
The full cleanup discipline for a team using worktrees is:
- For finished worktrees:
git worktree remove $PATH. If the worktree has uncommitted changes that are known disposable,git worktree remove --force. - For long-running worktrees:
git worktree lock --reason "$TICKET" $PATHat the start;git worktree unlock $PATHat the end. - For directories deleted outside Git:
git worktree prune --dry-run --verboseto verify what will be cleaned up; thengit worktree prune --verbose. - For CI runners using worktrees: a
trapthat always removes the worktree (covered in lesson 05) plus a dailygit worktree prunecron as a backstop. - For operators verifying the inventory:
git worktree listshould be the source of truth;lsof.git/worktrees/should match it line for line. If they diverge,git worktree prunebrings them back into sync.
Production discipline
- Default to
git worktree removefor finished worktrees. The directory and metadata are removed together; the branch is untouched. - Use
git worktree prune --dry-run --verbosefirst, thengit worktree prune --verbose. The dry run is a sanity check before destructive metadata removal. - Lock long-running worktrees at the start with a reason. The reason is the audit trail; future operators see why the worktree was locked and when the lock should be released.
- Include
git worktree prunein CI runner maintenance. A daily cron catches any orphaned worktree that survived the build trap. - Treat
git worktree listas the source of truth. If the list diverges from reality, prune reconciles them.
Cross-course references
- Git, CI/CD & GitOps - Part XVII (Reflog) covers the reflog expiry that affects detached-HEAD worktrees removed without branch promotion — a separate cleanup concern from prune.
- CI/CD Pipeline Patterns - Part III (CheckoutStrategies) covers the trap-plus-prune cleanup discipline for worktree- based runners.
- Linux for Production Sysadmins - Part XX (Filesystems) covers the filesystem-level protections (chattr, mount flags) that complement the Git-level lock.
Quiz
Knowledge check · 4 questions
Q1. An engineer deletes a worktree directory with `rm -rf ~/work/iac-feature-iam`. `git worktree list` still shows the entry. What is the correct command to clean up the stale metadata?
Q2. `git worktree lock` does not prevent the linked directory from being deleted outside Git with filesystem tools.
Q3. Distinguish between `git worktree remove` and `git worktree prune` in terms of what each one removes and when each is appropriate.
Q4. Reconcile a stale worktree inventory after an external cleanup, lock a long-running worktree against accidental removal, and recommend the discipline that prevents the next occurrence.
A CI runner's disk filled up overnight and an operator manually deleted several `build-*` directories with `rm -rf` to recover space. `git worktree list` now shows phantom entries that point at directories that no longer exist. The runner also has a long-running `iac-rollout-2026-08-21` worktree that must not be removed during the 36-hour rollout. The team has no lock policy in place.
Passing score: 75%. Answers are checked in this browser.