Skill v1.0.1
currentAutomated scan100/100+3 new
version: "1.0.1" name: git-worktree-clean description: Clean up stale git worktrees with merged branch detection and disk usage report argument-hint: "[--dry-run]" effort: low when_to_use: "Use when cleaning up merged or stale worktrees." disable-model-invocation: true
Git worktree clean
Batch cleanup of stale git worktrees. Safely removes merged branches, reports disk usage, and handles unmerged branches interactively.
Core principle: Auto-clean merged worktrees, interactive review for unmerged, always report what was reclaimed.
Part of: Worktree Lifecycle Suite | `/git-worktree` | `/git-worktree-status` | `/git-worktree-remove`
Process
- List All Worktrees:
git worktree list - Classify Each: merged vs unmerged vs protected
- Calculate Disk Usage: Per-worktree size
- Auto Mode: Remove all merged worktrees (safe)
- Interactive Mode: Review unmerged worktrees one by one
- Database Cleanup Reminder: List DB branches to clean
- Report: Summary of actions taken and space reclaimed
Flags
| Flag | Effect | |
|---|---|---|
--dry-run | Preview what would be cleaned, no changes | |
--all | Include unmerged worktrees (interactive confirmation each) | |
--force | Remove all worktrees without confirmation (dangerous) |
Worktree discovery
# Get main branch nameMAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')MAIN_BRANCH=${MAIN_BRANCH:-main}# Protected branches (never auto-clean)PROTECTED="main master develop staging production"# List all worktrees (skip main working tree)git worktree list --porcelain | while read line; do# Parse worktree path and branch# Skip the main worktree (first entry)done
Classification
for WORKTREE in $WORKTREES; doBRANCH=$(git -C "$WORKTREE" rev-parse --abbrev-ref HEAD)# Skip protectedif echo "$PROTECTED" | grep -qw "$BRANCH"; thenecho "PROTECTED: $BRANCH (skipped)"continuefi# Check merge statusif git merge-base --is-ancestor "$BRANCH" "$MAIN_BRANCH" 2>/dev/null; thenecho "MERGED: $BRANCH → safe to remove"MERGED_LIST="$MERGED_LIST $WORKTREE"elseecho "UNMERGED: $BRANCH → requires review"UNMERGED_LIST="$UNMERGED_LIST $WORKTREE"fidone
Disk usage calculation
for WORKTREE in $ALL_WORKTREES; do# Calculate size excluding symlinked node_modulesSIZE=$(du -sh --exclude='node_modules' "$WORKTREE" 2>/dev/null | cut -f1)# Or on macOS:SIZE=$(du -sh -I 'node_modules' "$WORKTREE" 2>/dev/null | cut -f1)echo " $WORKTREE: $SIZE"done
Dry run mode
# --dry-run: show what would happen without making changesecho "=== Dry Run ==="echo ""echo "Would remove (merged):"for WT in $MERGED_LIST; doecho " $WT ($BRANCH) - $SIZE"doneecho ""echo "Would ask about (unmerged):"for WT in $UNMERGED_LIST; doecho " $WT ($BRANCH) - $SIZE - last commit: $(git log -1 --format='%s' $BRANCH)"doneecho ""echo "Total space to reclaim: $TOTAL_SIZE"echo ""echo "Run without --dry-run to execute."
Auto mode (default)
Only removes merged worktrees. Safe by default.
echo "Cleaning merged worktrees..."for WORKTREE in $MERGED_LIST; doBRANCH=$(git -C "$WORKTREE" rev-parse --abbrev-ref HEAD)# Remove worktreegit worktree remove "$WORKTREE"# Delete local branchgit branch -d "$BRANCH" 2>/dev/null# Delete remote branchgit push origin --delete "$BRANCH" 2>/dev/nullecho " Removed: $WORKTREE ($BRANCH)"done# Report unmerged (not touched)if [ -n "$UNMERGED_LIST" ]; thenecho ""echo "Unmerged worktrees (kept):"for WT in $UNMERGED_LIST; doecho " $WT - use /git-worktree-remove or --all to review"donefi
Interactive Mode (--all)
Reviews unmerged worktrees one by one:
for WORKTREE in $UNMERGED_LIST; doBRANCH=$(git -C "$WORKTREE" rev-parse --abbrev-ref HEAD)LAST_COMMIT=$(git log -1 --format='%h %s (%cr)' "$BRANCH")AHEAD=$(git rev-list --count "$MAIN_BRANCH".."$BRANCH")echo ""echo "Unmerged: $WORKTREE"echo " Branch: $BRANCH ($AHEAD commits ahead of $MAIN_BRANCH)"echo " Last commit: $LAST_COMMIT"echo " Size: $SIZE"echo ""echo " [r]emove [k]eep [s]kip remaining"# Wait for user decision per worktreedone
Report format
After cleanup:
=== Worktree Cleanup Report ===Removed (merged):.worktrees/feat/auth (feat/auth) - 2.3 MB.worktrees/fix/login-bug (fix/login-bug) - 1.1 MB.worktrees/chore/deps-update (chore/deps-update) - 0.8 MBKept (unmerged):.worktrees/feat/experimental (feat/experimental) - 4.2 MBLast commit: a1b2c3d "WIP: new auth flow" (3 days ago)Kept (protected):.worktrees/develop (develop)Space reclaimed: 4.2 MBWorktrees remaining: 2References pruned: yesDB branches to clean:neonctl branches delete feat-authneonctl branches delete fix-login-bugneonctl branches delete chore-deps-update
Dry run report:
=== Dry Run - No Changes Made ===Would remove (3 merged):.worktrees/feat/auth - 2.3 MB.worktrees/fix/login-bug - 1.1 MB.worktrees/chore/deps-update - 0.8 MBWould keep (1 unmerged):.worktrees/feat/experimental - 4.2 MBWould keep (1 protected):.worktrees/developPotential space savings: 4.2 MB
Quick reference
| Situation | Action | |
|---|---|---|
| Default (no flags) | Remove merged worktrees only | |
--dry-run | Preview without changes | |
--all | Merged (auto) + unmerged (interactive) | |
--force | Remove everything except protected | |
| Protected branch | Always kept | |
| Merged branch | Auto-removed | |
| Unmerged branch | Kept (default) or interactive (--all) | |
| DB branches detected | Reminder with exact commands |
Common mistakes
Running `--force` without `--dry-run` first
- Always preview with
--dry-runbefore force-cleaning
Forgetting DB branch cleanup
- Worktree cleanup doesn't auto-delete DB branches. Follow the reminder commands.
Not running cleanup regularly
- Stale worktrees accumulate disk space. Run
/git-worktree-clean --dry-runweekly.
Usage
/git-worktree-clean/git-worktree-clean --dry-run/git-worktree-clean --all
Flags: $ARGUMENTS