Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXIII · WorktreesWorktrees

Worktree cleanup and pruning — git worktree prune; stale metadata; the lock file

Advanced⏱ ~22 mingit

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

Not yet marked complete on this device.

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/.git file 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:

CommandRemoves directory?Removes metadata?Use case
git worktree remove $PATHYesYesRoutine cleanup of a worktree the engineer is finished with
git worktree remove --force $PATHYes (with possible uncommitted data loss)YesCleanup when the worktree has uncommitted changes that are known to be disposable
git worktree pruneNoYesCleanup 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 -rf of the directory — but if the directory is deleted outside Git, the lock does not protect the metadata; git worktree prune will eventually clean up the metadata unless --no-prune-locks is 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:

  1. For finished worktrees: git worktree remove $PATH. If the worktree has uncommitted changes that are known disposable, git worktree remove --force.
  2. For long-running worktrees: git worktree lock --reason "$TICKET" $PATH at the start; git worktree unlock $PATH at the end.
  3. For directories deleted outside Git: git worktree prune --dry-run --verbose to verify what will be cleaned up; then git worktree prune --verbose.
  4. For CI runners using worktrees: a trap that always removes the worktree (covered in lesson 05) plus a daily git worktree prune cron as a backstop.
  5. For operators verifying the inventory: git worktree list should be the source of truth; ls of .git/worktrees/ should match it line for line. If they diverge, git worktree prune brings them back into sync.

Production discipline

  1. Default to git worktree remove for finished worktrees. The directory and metadata are removed together; the branch is untouched.
  2. Use git worktree prune --dry-run --verbose first, then git worktree prune --verbose. The dry run is a sanity check before destructive metadata removal.
  3. 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.
  4. Include git worktree prune in CI runner maintenance. A daily cron catches any orphaned worktree that survived the build trap.
  5. Treat git worktree list as 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

  1. 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?

  2. Q2. `git worktree lock` does not prevent the linked directory from being deleted outside Git with filesystem tools.

  3. Q3. Distinguish between `git worktree remove` and `git worktree prune` in terms of what each one removes and when each is appropriate.

  4. 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.