From empire-git
Batch cleans up multiple stale Git worktrees (inactive >7 days and clean), missing directories, and orphaned local branches after pruning remotes. Supports --dry-run and --days N.
npx claudepluginhub marcoskichel/empire --plugin empire-git[--dry-run] [--days N]This skill is limited to using the following tools:
Scan for stale worktrees and orphaned branches, then let the user decide what to clean up. This is the batch housekeeping counterpart to `/empire-git:worktree-close` (which handles one worktree at a time).
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Scan for stale worktrees and orphaned branches, then let the user decide what to clean up. This is the batch housekeeping counterpart to /empire-git:worktree-close (which handles one worktree at a time).
User input: $ARGUMENTS
If --dry-run is in the arguments, report what would be cleaned up without making any changes.
If --days N is in the arguments (e.g. --days 14), use N as the staleness threshold. Otherwise default to 7 days. Note: worktree-list uses a softer 3-day threshold (informational warning); cleanup uses 7 days because removal is the action and the longer window reduces false positives.
git worktree list --porcelain
For each worktree (excluding the main working tree), gather:
git -C "<path>" log -1 --format="%ct" 2>/dev/null
git -C "<path>" status --porcelain 2>/dev/null | wc -l | tr -d ' '
Classify each worktree:
| Status | Criteria |
|---|---|
| Stale | Last commit more than --days (default 7) ago and clean (no uncommitted work) |
| Missing | Directory no longer exists on disk (stale git metadata) |
| Active | Recent commits or has uncommitted changes |
First, prune remote-tracking references so git knows which remote branches are gone:
git fetch --prune
Determine which branches must never be flagged as orphans:
# The repo's default branch (main, master, develop, trunk, …) — detected, not assumed
DEFAULT_BRANCH=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')
# Fallback 1: check well-known branch names
[ -z "$DEFAULT_BRANCH" ] && DEFAULT_BRANCH=$(git for-each-ref --format='%(refname:short)' refs/heads/main refs/heads/master refs/heads/trunk refs/heads/develop | head -n1)
# Fallback 2: use current HEAD (guaranteed as long as we are in a git repo)
[ -z "$DEFAULT_BRANCH" ] && DEFAULT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
# Fallback 3: pick the most-recently-committed local branch
[ -z "$DEFAULT_BRANCH" ] && DEFAULT_BRANCH=$(git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/heads | head -n1)
# Guard: if DEFAULT_BRANCH is still empty we cannot safely run merged checks
if [ -z "$DEFAULT_BRANCH" ]; then
echo "Warning: could not determine default branch — merged-check classification will be skipped."
fi
# The branch currently checked out in the main working tree
CURRENT_BRANCH=$(git -C "$(git rev-parse --show-toplevel)" branch --show-current)
# Branches checked out in any worktree (these are "in use" — git won't let you delete them anyway)
git worktree list --porcelain | awk '/^branch / { sub("refs/heads/", "", $2); print $2 }'
Then list candidate local branches, excluding the default branch, the current branch, and any branch that has a worktree:
git branch --format='%(refname:short)'
Filter that list in your head (or with grep -v) against $DEFAULT_BRANCH, $CURRENT_BRANCH, and the worktree branches above. Do not hardcode main|master — many repos use develop, trunk, or custom default branches, and the user may currently be checked out on a branch that isn't the default.
For each remaining local branch, check:
git branch --merged "$DEFAULT_BRANCH" | grep -w "<branch>"
git branch -vv --format='%(refname:short) %(upstream:track)' | grep '\[gone\]'
The [gone] marker means the branch once tracked a remote that no longer exists — a strong signal the PR was merged and the remote branch deleted.gh is available)
gh pr list --head "<branch>" --state open --json number 2>/dev/null
Classify:
| Status | Criteria |
|---|---|
| Remote deleted | Tracked a remote that's gone (PR likely merged and branch deleted on GitHub) |
| Merged orphan | Merged into $DEFAULT_BRANCH, no worktree, no remote — safe to delete |
| Unmerged orphan | Not merged into $DEFAULT_BRANCH, no worktree, no remote — may be abandoned work |
| Has open PR | Skip — still in use |
Show a summary grouped by action:
Worktree Cleanup Report
═══════════════════════
Stale worktrees (no commits in 7+ days, clean):
feat/old-experiment .claude/worktrees/feat-old-experiment-1a2b3c4d 12d ago
fix/typo-header .claude/worktrees/fix-typo-header-5e6f7a8b 8d ago
Missing worktrees (directory gone, metadata remains):
feat/deleted-thing (path no longer exists)
Branches with deleted remote (PR likely merged on GitHub):
feat/auth-refactor remote gone — safe to delete locally
fix/login-bug remote gone — safe to delete locally
Orphaned branches (no worktree, no remote):
feat/abandoned-idea merged into $DEFAULT_BRANCH — safe to delete
fix/half-done NOT merged — review before deleting
Active worktrees (no action needed):
feat/current-work .claude/worktrees/feat-current-work-9c0d1e2f 2h ago
If --dry-run, stop here.
Present cleanup options as a checklist — the user picks what to clean up:
git worktree prune to clear stale metadatagit branch -d for each (their PRs were merged)git branch -d for eachgit branch -D if they want to force)Ask which of these the user wants to proceed with. Don't auto-execute anything — the user confirms first.
For each action the user approved:
# Prune stale metadata
git worktree prune
# Close stale worktrees
git worktree remove "<path>"
# Delete merged orphan branches
git branch -d "<branch>"
Print a summary of what was done:
Cleanup complete:
2 stale worktrees removed
1 missing worktree pruned
3 merged orphan branches deleted
1 unmerged orphan branch kept (fix/half-done)
This is a housekeeping tool, not a destructive one. The default posture is conservative — show what could be cleaned up, let the user decide. The --dry-run flag makes it completely safe to explore.
Merged branches are safe to delete. Their commits live in the target branch. Unmerged branches might contain work the user forgot about — flag them but don't delete without explicit confirmation.
Use git worktree remove, not rm -rf. And use git branch -d (safe delete), not -D. If something refuses to delete, that's useful information — surface it.