About git-branch-cleanup
A skill for identifying local Git branches already merged into protected branches and removing eligible ones. It preserves protected and current branches, shows the merge anchor for each candidate, and requires confirmation before safe deletion.
Key features
- 01Find branches merged into local protected branches
- 02Keep protected and current branches
- 03Show each candidate's merge anchor
- 04Require confirmation before deletion
- 05Delete only with safe `git branch -d`
Use cases
- 01Remove local branches after merged work
- 02Review cleanup candidates before deletion
- 03Preserve branches that are protected or checked out
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ chann/ skills --skill git-branch-cleanupAfter installing, type/git-branch-cleanupin Claude Code to confirm it loaded.
Bundled filesagents/openai.yamlevals/evals.json
Details
From the project READMEGit Branch Cleanup
Overview
Find and delete local branches that have been fully merged into one of the protected branches, then are safe to remove. Protected names — main, master, dev, develop, development, stg, stage, staging, root — and the current branch are always kept.
Announce at start: "I'm using the git-branch-cleanup skill to delete local branches already merged into a protected branch."
Protected Branches
The following branch names are never deleted, regardless of merge status:
main, master, dev, develop, development, stg, stage, staging, root
The current branch is also never deleted.
Workflow
Step 1: Inventory
git fetch --prune 2>/dev/null || true # informational; keeps refs honest, don't error if offline
current=$(git branch --show-current)
[ -n "$current" ] || { echo "Detached HEAD; checkout a branch first." >&2; exit 1; }
protected=(main master dev develop development stg stage staging root)
# All local branch names
all=$(git for-each-ref --format='%(refname:short)' refs/heads/)
Step 2: Find which protected branches actually exist locally
Only protected branches that exist locally can serve as merge anchors:
anchors=()
for p in "${protected[@]}"; do
git rev-parse --verify "$p" >/dev/null 2>&1 && anchors+=("$p")
done
if [ "${#anchors[@]}" -eq 0 ]; then
echo "No protected branches (main/master/dev/develop/development/stg/stage/staging/root) found locally; nothing to anchor against." >&2
exit 1
fi
Step 3: Identify candidates
A branch is a candidate when all of:
- It is not in the protected list
- It is not the current branch
- Its tip is reachable from at least one anchor (i.e. fully merged into it)
candidates=()
for b in $all; do
# skip protected
case " ${protected[*]} " in *" $b "*) continue ;; esac
# skip current
[ "$b" = "$current" ] && continue
# merged into any anchor?
for a in "${anchors[@]}"; do
if git merge-base --is-ancestor "$b" "$a"; then
candidates+=("$b|$a") # remember which anchor proved it
break
fi
done
done
Each entry stores branch|anchor so the plan can show why each candidate is being deleted.
Step 4: Show the plan and confirm
Plan: delete N merged local branches
feature/oauth-login (merged into main)
bugfix/null-response (merged into main)
chore/update-deps (merged into develop)
Kept (protected or current):
main, develop, stage ← protected
feature/in-progress ← current branch
Proceed? (y/N)
If candidates is empty → tell the user "Nothing to clean up; no merged non-protected branches found." and stop.
Wait for explicit confirmation. Default is no.
Step 5: Delete with -d (safe)
for entry in "${candidates[@]}"; do
branch="${entry%%|*}"
if git branch -d "$branch"; then
echo "✓ deleted $branch"
else
echo "✗ refused $branch — left intact (run \`git branch -d $branch\` manually if you're sure)"
fi
done
Never use -D (force). If -d refuses for a candidate that we thought was merged, something is off — report and skip; don't escalate.
Step 6: Summary
git branch # show what remains
echo "Done. Deleted M of N candidates."
If any candidates were skipped, list them and explain that -d refused (likely the branch has commits not actually reachable from the anchor — e.g. a --no-commit cherry-pick or rewritten history).
Worked Example
User: 머지된 브랜치 다 정리해줘
current = main
anchors found locally: main, develop
Local branches:
main ← protected, kept
develop ← protected, kept
feature/oauth-login → ancestor of main? yes → candidate
bugfix/null-response → ancestor of main? yes → candidate
feature/wip-experiment → ancestor of any anchor? no → kept (not yet merged)
hotfix/typo → ancestor of develop? yes → candidate
Plan: delete 3 (feature/oauth-login, bugfix/null-response, hotfix/typo)
User confirms.
→ git branch -d feature/oauth-login ✓
→ git branch -d bugfix/null-response ✓
→ git branch -d hotfix/typo ✓
→ Show remaining branches: main, develop, feature/wip-experiment.
Red Flags
Never:
- Use
git branch -D(force delete) — only-d(safe delete) is allowed here - Delete any branch in the protected list (
main,master,dev,develop,development,stg,stage,staging,root) - Delete the current branch
- Delete remote branches (
git push origin --delete ...) — this skill is local-only - Skip the confirmation prompt
- Treat unmerged-but-stale branches as candidates — only fully-merged branches qualify
Always:
- Show the plan with the proving anchor for each candidate (so the user can spot-check)
- Show what was kept and why
- Use
git merge-base --is-ancestorto verify merge status (notgit branch --merged, which only checks against HEAD) - Default the confirmation to "no"
Common Mistakes
Using git branch --merged instead of --is-ancestor
- Problem:
--mergedchecks against the current branch only, not all protected anchors - Fix: Loop through anchors with
git merge-base --is-ancestorso a branch merged intodevelop(but notmain) is still detected
Force-deleting when -d refuses
- Problem: Reaching for
-Dwhen the safety check correctly flags risk - Fix: Skip the branch and report; let the user decide
Including the current branch as a candidate
- Problem: You can't delete the branch you're standing on; git will refuse anyway
- Fix: Filter the current branch out before building the candidate list
Integration
Pairs with:
git-merge-to-main/git-merge-to-dev— those skills delete a single source branch right after merging; this skill cleans up everything that's already merged
Called by: Manual user invocation only. Never auto-run during another skill's workflow.