name: worktree-hygiene
description: Reap merged git worktrees and branches safely across the warp, codex, opencode, and conductor harnesses. Use when worktrees pile up, after a branch merges, when cleaning up git worktree list, removing an orphaned or detached worktree, or deciding whether a worktree is safe to delete. Not for creating branches or authoring commits (use the git skill).
metadata:
author: epicenter
version: '1.0'
Worktree Hygiene
Related Skills: git for branch creation, staging, and commit messages. standalone-commits for reviewable units. pull-request for merge strategy.
Four harnesses (warp, codex, opencode, conductor) mint worktrees here and nothing reaps them, so they accumulate. The cure is to reap on close: when a branch lands, remove its worktree and delete the branch in the same motion. This skill is the safe procedure for doing that, one-off or as a periodic sweep.
The reap signal (and the trap)
"Branch merged" is not sufficient to reap. A merged branch routinely carries live uncommitted edits in its working tree; removing it with --force destroys that work. The real signal is:
reap = (branch fully contained in origin/main) AND (working tree clean OR only throwaway dirt)
Use git merge-base --is-ancestor <branch> origin/main for containment: if true, every commit on the branch is in origin/main, which is airtight even when the branch sits on a stacked base. git cherry origin/main <branch> shows unmerged commits (+ lines).
Audit procedure
Fetch first if origin/main is stale, then classify every worktree:
cd <main-checkout>; base=origin/main
git worktree list --porcelain | awk '/^branch /{print $2}' | sed 's#refs/heads/##' | while read b; do
[ -z "$b" ] && continue
ahead=$(git cherry "$base" "$b" 2>/dev/null | grep -c '^+')
if git merge-base --is-ancestor "$b" "$base" 2>/dev/null; then st="MERGED"; else st="$ahead unmerged"; fi
printf "%-50s %s\n" "$b" "$st"
done
For each MERGED worktree, check its working tree with git -C <path> status --porcelain (no output = clean). Inspect dirty file lists before deciding: lockfile-only churn is throwaway; modified source or unique untracked files are real work. Detached-HEAD worktrees have no branch line; test their tip with git merge-base --is-ancestor <sha> origin/main.
Decide per worktree
merged + clean -> reap: git worktree remove <path>; git branch -d/-D <branch>
merged + throwaway dirt only -> reap: git worktree remove --force <path>; git branch -d/-D <branch>
detached + merged + clean -> reap: git worktree remove <path> (no branch to delete)
merged + REAL uncommitted work -> preserve. Branch or commit the work first; do not reap.
detached + REAL uncommitted -> anchor before anything: git -C <path> switch -c holding/<name>
unmerged commits -> leave alone (active work)
Reaping is destructive: gate it
git worktree remove and git branch -d are destructive (AGENTS.md: destructive actions need approval). For a sweep, present an approve-list of exact commands grouped by tier before running any of them. Never reap on a self-reported "it's merged"; run the audit yourself.
git branch -drefuses unless the branch is merged into the current HEAD. If localmainlagsorigin/main, it will refuse a branch that is merged only on the remote;-Dis then justified by themerge-base --is-ancestor origin/mainproof.git worktree removecan fail withDirectory not emptywhen ignored files (node_modules,.DS_Store) remain. It still de-registers the worktree. Finish withgit worktree prunethenrm -rf <path>.- The harnesses spawn and mutate worktrees continuously, even mid-sweep. Re-run the audit at the end; treat reaping as ongoing discipline, not a one-time event.
Repo-specific worktree gotchas
git stashis shared across all worktrees here. A baregit stash/stash popcan grab another worktree's entry. Prefer explicit branches; avoid stash.- Animal-named warp branches are stacked on unmerged bases, cut from whatever was checked out rather than clean
origin/main. Before opening a PR from one, rungit diff --stat origin/main...HEADand confirm scope; unrelated work rides along otherwise. - Verify HEAD before committing in a worktree (
git rev-parse --abbrev-ref HEAD). A handoff may claim a "fresh worktree on branch X" that does not exist; these stay checked out on the base branch itself.
Invariant: one owner per worktree
Each worktree and branch has a single owner (the harness/agent that created it). Do not reap or commit into a worktree you do not own without confirming it is abandoned; a clean-looking tree may belong to a live session.
Final checks
- Re-run the audit; confirm reaped paths are gone with
git worktree list. git worktree pruneto clear stale registrations.- Confirm any anchored
holding/*branches still hold their edits.