Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Safely analyzes and cleans up local git branches and worktrees by categorizing them as merged, squash-merged, superseded, or active work.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-10 | ✗→✓ | ▲ Improved | 73% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 79% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 191% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 242% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 469% | 0% |
Safely clean up accumulated git worktrees and local branches by categorizing them into: safely deletable (merged), potentially related (similar themes), and active work (keep).
Never delete anything without explicit user confirmation. This skill uses a gated workflow where users must approve each step before any destructive action.
IMPORTANT: git branch -d will ALWAYS fail for squash-merged branches because git cannot detect that the work was incorporated. This is expected behavior, not an error.
When you identify a branch as squash-merged:
git branch -D (force delete) from the startgit branch -d first and then ask again for -D - this wastes user confirmationsgit branch -D for squash-merged branchesMANDATORY: Before categorizing individual branches, group them by name prefix:
bash# Extract common prefixes from branch names # e.g., feature/auth-*, feature/api-*, fix/login-*
Branches sharing a prefix (e.g., feature/api, feature/api-v2, feature/api-refactor) are almost certainly related iterations. Analyze them as a group:
Present related branches together with a clear recommendation, not scattered across categories.
Don't rely on simple keyword matching. For [gone] branches:
bash# 1. Get the branch's commits that aren't in default branch git log --oneline "$default_branch".."$branch" # 2. Search default branch for PRs that incorporated this work # Search by: branch name, commit message keywords, PR numbers git log --oneline "$default_branch" | grep -iE "(branch-name|keyword|#[0-9]+)" # 3. For related branch groups, trace which PRs merged which work git log --oneline "$default_branch" | grep -iE "(#[0-9]+)" | head -20
Gather ALL information upfront before any categorization:
bash# Get default branch name default_branch=$(git symbolic-ref refs/remotes/origin/HEAD \ 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main") # Protected branches - never analyze or delete protected='^(main|master|develop|release/.*)$' # List all local branches with tracking info git branch -vv # List all worktrees git worktree list # Fetch and prune to sync remote state git fetch --prune # Get merged branches (into default branch) git branch --merged "$default_branch" # Get recent PR merge history (squash-merge detection) git log --oneline "$default_branch" | grep -iE "#[0-9]+" | head -30 # For EACH non-protected branch, get unique commits and sync status for branch in $(git branch --format='%(refname:short)' \ | grep -vE "$protected"); do echo "=== $branch ===" echo "Commits not in $default_branch:" git log --oneline "$default_branch".."$branch" 2>/dev/null \ | head -5 echo "Commits not pushed to remote:" git log --oneline "origin/$branch".."$branch" 2>/dev/null \ | head -5 || echo "(no remote tracking)" done
Note on branch names: Git branch names can contain characters that break shell expansion. Always quote "$branch" in commands.
Do this BEFORE individual categorization.
Identify branch groups by shared prefixes:
bash# List branches and extract prefixes git branch --format='%(refname:short)' | sed 's/-[^-]*$//' | sort | uniq -c | sort -rn
For each group with 2+ branches:
SUPERSEDED requires evidence, not just shared prefix:
Example analysis for feature/api-* branches:
markdown### Related Branch Group: feature/api-* | Branch | Commits | PR Merged | Status | |--------|---------|-----------|--------| | feature/api | 12 | #29 (initial API) | Superseded - work in main | | feature/api-v2 | 8 | #45 (API improvements) | Superseded - work in main | | feature/api-refactor | 5 | #67 (refactor) | Superseded - work in main | | feature/api-final | 4 | None found | Superseded by above PRs | **Recommendation:** All 4 branches can be deleted - work incorporated via PRs #29, #45, #67
For branches NOT in a related group, categorize individually:
Is branch merged into default branch?
├─ YES → SAFE_TO_DELETE (use -d)
└─ NO → Is tracking a remote?
├─ YES → Remote deleted? ([gone])
│ ├─ YES → Was work squash-merged? (check main for PR)
│ │ ├─ YES → SQUASH_MERGED (use -D)
│ │ └─ NO → REMOTE_GONE (needs review)
│ └─ NO → Local ahead of remote? (check: git log origin/<branch>..<branch>)
│ ├─ YES (has output) → UNPUSHED_WORK (keep)
│ └─ NO (empty output) → SYNCED_WITH_REMOTE (keep)
└─ NO → Has unique commits?
├─ YES → LOCAL_WORK (keep)
└─ NO → SAFE_TO_DELETE (use -d)Category definitions:
| Category | Meaning | Delete Command | |----------|---------|----------------| | SAFE_TO_DELETE | Merged into default branch | git branch -d | | SQUASH_MERGED | Work incorporated via squash merge | git branch -D | | SUPERSEDED | Part of a group, work verified in main via PR or in newer branch | git branch -D | | REMOTE_GONE | Remote deleted, work NOT found in main | Review needed | | UNPUSHED_WORK | Has commits not pushed to remote | Keep | | LOCAL_WORK | Untracked branch with unique commits | Keep | | SYNCED_WITH_REMOTE | Up to date with remote | Keep |
Check ALL worktrees and current directory for uncommitted changes:
bash# For each worktree path git -C <worktree-path> status --porcelain # For current directory git status --porcelain
Display warnings prominently:
markdownWARNING: ../proj-auth has uncommitted changes: M src/auth.js ?? new-file.txt These changes will be LOST if you remove this worktree.
Present everything in ONE comprehensive view. Group related branches together:
markdown## Git Cleanup Analysis ### Related Branch Groups **Group: feature/api-* (4 branches)** | Branch | Status | Evidence | |--------|--------|----------| | feature/api | Superseded | Work merged in PR #29 | | feature/api-v2 | Superseded | Work merged in PR #45 | | feature/api-refactor | Superseded | Work merged in PR #67 | | feature/api-final | Superseded | Older iteration, diverged | Recommendation: Delete all 4 (work is in main) --- ### Individual Branches **Safe to Delete (merged with -d)** | Branch | Merged Into | |--------|-------------| | fix/typo | main | **Safe to Delete (squash-merged, requires -D)** | Branch | Merged As | |--------|-----------| | feature/login | PR #42 | **Needs Review ([gone] remotes, no PR found)** | Branch | Last Commit | |--------|-------------| | experiment/old | abc1234 "WIP something" | **Keep (active work)** | Branch | Status | |--------|--------| | wip/new-feature | 5 unpushed commits | ### Worktrees | Path | Branch | Status | |------|--------|--------| | ../proj-auth | feature/auth | STALE (merged) | --- **Summary:** - 4 related branches (feature/api-*) - recommend delete all - 1 merged branch - safe to delete - 1 squash-merged branch - safe to delete - 1 needs review - 1 to keep Which would you like to clean up?
Use AskUserQuestion with clear options:
Do not proceed until user responds.
Show the EXACT commands that will run, with correct flags:
markdownI will execute: # Merged branches (safe delete) git branch -d fix/typo # Squash-merged branches (force delete - work is in main via PRs) git branch -D feature/login git branch -D feature/api git branch -D feature/api-v2 git branch -D feature/api-refactor git branch -D feature/api-final # Worktrees git worktree remove ../proj-auth Confirm? (yes/no)
IMPORTANT: This is the ONLY confirmation needed for deletion. Do not add extra confirmations if -D is required.
Run each deletion as a separate command so partial failures don't block remaining deletions. Report the result of each:
bashgit branch -d fix/typo git branch -D feature/login git branch -D feature/api git branch -D feature/api-v2 git branch -D feature/api-refactor git branch -D feature/api-final git worktree remove ../proj-auth
If a deletion fails, report the error and continue with remaining deletions.
markdown## Cleanup Complete ### Deleted - fix/typo - feature/login - feature/api - feature/api-v2 - feature/api-refactor - feature/api-final - Worktree: ../proj-auth ### Remaining (4 branches) | Branch | Status | |--------|--------| | main | current | | wip/new-feature | active work | | experiment/old | needs review |
/git-cleanup-d for merged, -D for squash-merged/supersededThese are common shortcuts that lead to data loss. Reject them:
| Rationalization | Why It's Wrong | |-----------------|----------------| | "The branch is old, it's probably safe to delete" | Age doesn't indicate merge status. Old branches may contain unmerged work. | | "I can recover from reflog if needed" | Reflog entries expire. Users often don't know how to use reflog. Don't rely on it as a safety net. | | "It's just a local branch, nothing important" | Local branches may contain the only copy of work not pushed anywhere. | | "The PR was merged, so the branch is safe" | Squash merges don't preserve branch history. Verify the specific commits were incorporated. | | "I'll just delete all the [gone] branches" | [gone] only means the remote was deleted. The local branch may have unpushed commits. | | "The user seems to want everything deleted" | Always present analysis first. Let the user choose what to delete. | | "The branch has commits not in main, so it has unpushed work" | "Not in main" ≠ "not pushed". A branch can be synced with its remote but not merged to main. Always check git log origin/<branch>..<branch>. |
Other measured skills in the registry, with their headline benchmark lift.