Git branch cleanup
Skill magnusrodseth/dotfiles/.claude/skills/git-branch-cleanup
⚙️ There are many like them, but these dotfiles are mine. A stow-managed macOS setup: Zsh, Neovim, tmux, Ghostty, and a pile of Claude Code tooling.
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.
What its author says it does
Copied from the file, not written here
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.
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.
Gives 0 of the 12 instructions most pr commit review skills give in 785 tokens
Counted across 888 of the 1,342 authors here whose files we hold, read 2026-08-06
- use conventional commits formatin 123 of 888, across 110 files
- keep subject line under 72 charactersin 60 of 888, across 46 files
- delete branches after mergein 50 of 888, across 37 files
- use imperative mood in subject linein 50 of 888, across 41 files
- use imperative mood in commit messagesin 45 of 888
- generate a conventional commit messagein 42 of 888
- make atomic commitsin 37 of 888, across 25 files
- run tests before committingin 36 of 888, across 24 files
- run project test suite to verify clean baselinein 35 of 888, across 7 files
- run detected project setup commandsin 34 of 888, across 6 files
- wrap commit body at 72 charactersin 32 of 888, across 25 files
- split unrelated changes into separate commitsin 32 of 888, across 27 files
Said here and by no other author read
- run the classification script
- review classified branches with the user
- re-run with force only after confirmation
- use git branch -D only with force
- inform user deletions are recoverable via reflog
- enumerate branches using git for-each-ref
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.