Git branch cleanup
Skill magnusrodseth/dotfiles/.claude/skills/git-branch-cleanup
Safely delete local git branches that are already merged, squash/rebase-merged, or whose remote was deleted, while protecting the current branch, the default branch, and any worktree-checked-out branch. Use when the user wants to clean up, prune, tidy, or delete "done"/merged/stale local branches, shrink a long `git branch` list, or remove branches after merging a PR.From its SKILL.md
npx -y skills add magnusrodseth/dotfiles --skill git-branch-cleanupAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
3.5 KB, 785 tokens by cl100k_base, as published. Nobody here has run it
git-branch-cleanup
Remove finished local branches without risking unmerged work. The hard part is
that git branch --merged only sees branches reachable from the default branch —
it MISSES squash- and rebase-merged PRs (the common GitHub workflow). This skill
layers three signals and only auto-deletes the provably-safe set.
Quick start
bash scripts/cleanup-branches.sh --fetch # delete safe-merged, list the rest
bash scripts/cleanup-branches.sh --dry-run # classify only, delete nothing
bash scripts/cleanup-branches.sh --fetch --force # also -D the PR-merged / gone set
The script classifies every local branch into four buckets, deletes the merged
bucket with safe git branch -d, and prints the rest. Present its output to the
user; only re-run with --force after they confirm the squash-merged / gone set.
What it protects (never deleted)
- The current branch and the default branch (
origin/HEAD, else main/master/develop) - Any branch checked out in a worktree (git refuses these anyway; the script skips them)
Classification
| Bucket | Signal | Action |
|---|---|---|
| Merged | git for-each-ref --merged=<default> (ancestor of default) | auto git branch -d (safe) |
| PR-merged | gh pr list --state merged head matches branch | list; -D only with --force |
| Gone | upstream tracking shows [gone] (remote branch deleted) | list; -D only with --force |
| Active | none of the above | KEPT |
Workflow
git fetch --prune(or pass--fetch) so[gone]detection is accurate.- Run the script; review the four buckets with the user.
- The merged bucket is already gone (safe
-d). For the PR-merged / gone buckets, confirm with the user, then re-run with--force(or delete individually). - Mention deletions are recoverable from reflog (
git reflog) for ~30 days.
Safety rules
- Prefer
git branch -d(refuses unmerged work). Use-Donly for branches confirmed merged via a PR or whose remote is[gone], and only after confirmation. - Never delete the current branch, default branch, or worktree branches.
- Squash/rebase-merged branches are NOT ancestors of the default branch, so
-dwill refuse them — that is expected; rely on thegh/[gone]signals instead.
Manual fallback (no script / explaining the steps)
default=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's#origin/##')
git for-each-ref --merged="$default" --format='%(refname:short)' refs/heads/ # merged
git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads/ \
| grep '\[gone\]' # gone
gh pr list --state merged --limit 300 --json headRefName --jq '.[].headRefName' # squash-merged
git worktree list # protect these
Footgun encoded here: enumerate with
git for-each-ref(porcelain), nevergit branch | grep. A naivegrep -v mainalso strikes "maintenance", "domain", etc. Use exact, anchored matches.
What ships with it: 1 file
5.1 KB alongside SKILL.md, 1 of them executable
scripts/
- cleanup-branches.shruns5.1 KB