Branch Cleanup Skill
This skill provides an iterative workflow for cleaning up git branches by analyzing their merge status and organizing them into appropriate namespaces.
Quick Start - Run Analysis Script
ALWAYS run this script first to save tokens:
python .claude/skills/clean-branches/analyze_branches.py
This script:
- Fetches all branches and analyzes merge/containment status
- Outputs a formatted markdown report
- Identifies branches needing action with recommendations
After reviewing the output, proceed with user approval for any actions (delete, move, archive).
Branch Organization Schema
| Namespace | Purpose | Example |
|---|---|---|
archive/completed/<name> |
Merged branches (work completed) | archive/completed/feature-login |
archive/stopped/<name> |
Unmerged branches (abandoned work) | archive/stopped/experiment-x |
wip/<name> |
Work in progress (active development) | wip/new-feature |
| (root) | Keep as-is | feature-y |
Workflow
Step 1: Identify Default Branch
Query GitHub to determine the default branch:
gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'
Step 2: Fetch All Remote Branches
git fetch --all --prune
Step 3: List All Branches
Get all local and remote branches:
# Local branches
git branch --list
# Remote branches (excluding HEAD)
git branch -r | grep -v HEAD
Step 4: Analyze Local Branches
For each LOCAL branch (excluding the default branch and already-archived branches):
Check if merged into default branch:
git branch --merged <default-branch> | grep -q <branch-name>Get last commit info:
git log -1 --format="%h %s (%cr by %an)" <branch-name>Check if remote exists:
git ls-remote --heads origin <branch-name>CRITICAL: For local-only branches, check if contained in other branches:
If a branch has no remote, check if its commits are already contained in main or any archived branch:
# Check if branch is ancestor of (contained in) main git merge-base --is-ancestor <branch> main && echo "Contained in main" # Check if branch is ancestor of any archived branch for archived in $(git branch -r | grep "origin/archive/"); do git merge-base --is-ancestor <branch> $archived 2>/dev/null && echo "Contained in $archived" done # Alternative: show all branches that contain this branch's HEAD git branch -a --contains <branch>Interpretation:
- If contained in
main→ Work was merged, safe to delete local branch - If contained in
archive/completed/*→ Work was completed, safe to delete local branch - If contained in
archive/stopped/*→ Work was archived, safe to delete local branch - If NOT contained anywhere → Work may be lost if deleted, ask user carefully
- If contained in
Step 5: Analyze Remote-Only Branches (CRITICAL!)
This step is often missed! Check remote branches that have NO local copy and are NOT already archived:
# List remote branches not merged into default branch
git branch -r --no-merged main | grep -v HEAD | grep -v "archive/" | grep -v "wip/"
For each remote-only branch found:
Get last commit info:
git log -1 --format="%h %s (%cr by %an)" origin/<branch-name>Check if contained in default branch or other branches:
# Check what branches contain this remote branch git branch -a --contains origin/<branch-name>Check merge status:
# Is it merged into main? git merge-base --is-ancestor origin/<branch-name> main && echo "Merged into main" # Is it merged into the current working branch? git merge-base --is-ancestor origin/<branch-name> HEAD && echo "Merged into HEAD"
Actions for remote-only branches:
- If contained in
main→ Move toarchive/completed/(work was merged) - If contained in current branch but not main → Ask user (might be pending merge)
- If NOT contained anywhere → Ask user: archive/stopped or keep for future work
# Move remote branch to archive/completed (if merged)
git push origin origin/<branch>:refs/heads/archive/completed/<branch>
git push origin --delete <branch>
# Move remote branch to archive/stopped (if abandoned)
git push origin origin/<branch>:refs/heads/archive/stopped/<branch>
git push origin --delete <branch>
Step 6: Generate Report
Present a summary table to the user:
| Branch | Status | Last Commit | Age | Remote | Contained In | Recommendation |
|---|---|---|---|---|---|---|
| feature-x | Merged | abc123 Fix bug | 2 weeks | Yes | main | → delete local (work in main) |
| experiment-y | Unmerged | def456 WIP | 3 months | No | archive/stopped/exp-y | → delete local (already archived) |
| new-feature | Unmerged | ghi789 Add X | 1 day | No | (none) | → ask user: WIP/stop/keep |
Key insight: If "Contained In" shows another branch, the work is NOT lost - it's safe to delete the local branch.
Step 7: Delete Contained Local-Only Branches
For local branches that have no remote but ARE contained in another branch (main or archive/*), the work is already preserved elsewhere. These can be safely deleted:
# Delete local branch that's already contained in main or archive
git branch -d <branch>
Note: Use -d (not -D) which will fail if the branch isn't actually merged/contained - this is a safety check.
Step 8: Process Merged Branches (with remotes)
For branches that have remotes and are confirmed as merged, offer these options:
Options (in order of preference):
- Archive remote + delete local (Recommended) - Move remote to archive, delete local copy
- Delete both - Delete both local and remote (work already in target branch)
- Keep - Leave as-is
Archive remote + delete local (best option - preserves history on remote):
# Move remote branch to archive
git push origin origin/<branch>:refs/heads/archive/completed/<branch>
# Delete old remote branch
git push origin --delete <branch>
# Delete local branch
git branch -D <branch>
Delete both (when you don't need the branch history):
# Delete remote
git push origin --delete <branch>
# Delete local
git branch -D <branch>
Step 9: Handle Truly Unmerged Branches
For each unmerged branch, ask the user using AskUserQuestion:
- Keep: Leave branch as-is
- WIP: Move to
wip/<branch-name> - Stop: Move to
archive/stopped/<branch-name>
Then execute the chosen action:
# For WIP
git branch -m <branch> wip/<branch>
git push origin wip/<branch>
git push origin --delete <branch>
# For Stop
git branch -m <branch> archive/stopped/<branch>
git push origin archive/stopped/<branch>
git push origin --delete <branch>
Step 10: Clean Up Synced Archive Branches
Local archive branches that are synced with remote are redundant - they're safely backed up. Offer to delete them:
Identify synced archive branches:
# Find local archive branches that have matching remote for branch in $(git branch --list 'archive/*'); do branch_name=$(echo "$branch" | sed 's/^[* ]*//') if git ls-remote --heads origin "$branch_name" | grep -q .; then echo "$branch_name" # Has remote backup, safe to delete locally fi donePresent to user:
Local Archive Branch Remote Status Recommendation archive/completed/feature-x ✅ Synced Delete local (backed up) archive/image-bug ✅ Synced Delete local (backed up) Offer bulk deletion: Ask using AskUserQuestion:
- Delete all synced: Remove all local archive branches that have remote backups
- Review each: Go through them one by one
- Keep all: Leave local copies
Execute deletion:
git branch -D <archive-branch> # Safe - remote copy exists
Step 11: Iterate
After processing, show updated branch list and ask if further cleanup is needed. Repeat until the user is satisfied.
Important Notes
- CRITICAL: EVERY action with side effects (delete, rename, move, push) MUST be approved by the user using AskUserQuestion BEFORE execution
- Query/read operations (git log, git branch --list, git branch --contains, etc.) do NOT require approval
- Never batch multiple branch operations - ask for approval for each branch individually or show a clear list and get explicit confirmation
- Even if analysis shows a branch is "safe to delete", still ask the user first
- Skip branches that are already in
archive/orwip/namespaces (no action needed) - Archive branches: When analyzing, distinguish between:
- Local-only archives: May want to push to remote first or delete
- Synced archives: Safe to delete locally (backed up on remote)
- Remote-only archives: No action needed (already clean locally)
- Handle branches that only exist locally or only on remote
- If a branch has no remote tracking, note this in the report
- Preserve the current checked-out branch (cannot delete/rename it while on it)
User Approval Flow
- Present the analysis - Show the full report with recommendations
- Ask for approval - Use AskUserQuestion for each branch or group of branches
- Execute only after approval - Never assume user consent
- Report results - Show what was done after each action
Example:
Analysis shows: branch-x is contained in main, safe to delete
[AskUserQuestion]: "Delete local branch `branch-x`? (already in main)"
- Yes, delete
- No, keep
[Only proceed if user selects "Yes"]