name: nv-worktree-cleanup
description: >-
Audits git worktrees and removes the ones that are safe to delete — merged
into the base branch (including squash-merges via gh), missing on disk,
or explicitly confirmed by the user — then deletes the associated local
branches and runs git worktree prune. Use when the user asks to clean up
worktrees, prune worktrees, list stale worktrees, remove merged worktrees
or branches, or reclaim disk space from old checkouts.
disable-model-invocation: true
Clean up git worktrees
End state: stale, merged, and missing worktrees are removed; their local branches are deleted; git worktree prune has run; the user has signed off on every deletion.
Hard rules
- Never
--forceremove a worktree without explicit user confirmation. Dirty worktrees stay until the user says otherwise. - Never delete the main worktree (the one at
git rev-parse --show-toplevelof the primary checkout) or the worktree the agent is currently sitting in. Detect the agent's worktree by comparingpwd(orgit rev-parse --show-toplevelfrom the current directory) against each path fromgit worktree list. If they match, refuse deletion and tell the user to move first viamove_agent_to_root. - Confirm deletions in one batch via
AskQuestionwithallow_multiple: true. Don't delete one-by-one without a single approval step. - Use
git branch -d(safe delete) by default. Only use-Dfor branches the user explicitly flags as okay to force-delete.
Workflow
1. Enumerate worktrees
git worktree list --porcelain
Parse into entries: path, branch, HEAD. Skip the primary worktree.
2. Determine the base branch
Check in order:
git symbolic-ref refs/remotes/origin/HEAD(gives e.g.refs/remotes/origin/main)- Fall back to
main, thennext, thenmaster
Use origin/<base> for merge checks so local staleness doesn't matter. Run git fetch --prune origin first to make checks accurate.
3. Classify each worktree
For each non-primary worktree, compute one of:
| Status | Detection | Default action |
|---|---|---|
| missing | path does not exist on disk | prune only |
| merged-direct | git merge-base --is-ancestor <branch> origin/<base> succeeds |
remove + delete branch |
| merged-squash | gh pr list --state merged --head <branch> --json number returns a PR (requires GitHub CLI — if gh is missing, skip this classification and flag the branch as active with a note to install gh or verify merge manually) |
remove + delete branch (with -D after confirm, since -d will refuse) |
| dirty | git -C <path> status --porcelain is non-empty |
flag, do not remove |
| unpushed | branch has commits not on origin/<branch> and no merged PR |
flag, do not remove |
| active | none of the above | leave alone |
Also flag any worktree that is the current agent root — compare pwd / git rev-parse --show-toplevel against each worktree path, mark matches as such, and exclude them from the removable set.
4. Present the summary and get one consolidated approval
Use AskQuestion with allow_multiple: true. Group by category in the prompt so the user can scan it. For each removable entry, the option label should include path, branch, and reason. Example option labels:
merged: ../more-dcr-oauths (branch more-dcr-oauths)missing: ../old-experiment (branch old-experiment)dirty: ../wip-bar (branch wip/bar) — has uncommitted changes
For dirty / unpushed entries, surface them as separate options so the user can opt in to force-removing if they really want to.
5. Execute confirmed removals
For each confirmed worktree:
git worktree remove <path> # safe path
git worktree remove --force <path> # only if user opted in for dirty
git branch -d <branch> # safe delete (refuses if unmerged)
git branch -D <branch> # only if user opted in (squash-merged or force); squash-merges leave the branch ahead/unmerged in topology so `-d` refuses — confirm merge via `gh pr list` first
For missing entries, skip remove and rely on prune in the next step.
6. Finalize
git worktree prune
git worktree list # show the user the new state
Report:
- Removed worktrees (with branch names)
- Skipped worktrees and why (dirty/unpushed/active)
- Any branch deletions that failed and why
Anti-patterns
- Don't loop
git worktree remove --forceover every entry "to be safe". Force is for explicit user opt-in only. - Don't delete branches before removing the worktree that has them checked out —
git branch -dwill fail with "checked out at ...". Order is: remove worktree, then delete branch. - Don't trust a local "branch is behind origin/main" as proof of merge. Squash-merges leave the branch ahead and unmerged in topology; use
gh pr list --state merged --head <branch>for that case. - Don't run
git worktree prunebefore listing — you'll lose the metadata you need to identify missing entries. - Don't act without
git fetch --prune originfirst; otherwise "merged into origin/main" checks can be wrong.
Example session
User: "clean up my worktrees"
Agent flow:
git fetch --prune origingit worktree list --porcelain→ 5 worktrees (1 primary + 4 others)- Classify:
../nv-100-x→ merged-direct../nv-200-y→ merged-squash (PR #4321 merged)../nv-300-z→ dirty (3 modified files)../old-experiment→ missing on disk
AskQuestion: which of these to remove? (multi-select, dirty entry pre-flagged with a warning label)- User picks the two merged ones + missing.
git worktree remove ../nv-100-x && git branch -d nv-100/xgit worktree remove ../nv-200-y && git branch -D nv-200/y(squash-merge needed-D, user confirmed)git worktree prune(cleans up../old-experiment)- Report: 3 removed, 1 skipped (dirty), final
git worktree list.