Skip to content
Back to skills

Git Branch Cleanup

ASecurity

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.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 2, 2026
ai-agentsgobashgit

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 2, 2026

npx -y skills add magnusrodseth/dotfiles --skill git-branch-cleanup --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Git Branch Cleanup?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Git Branch Cleanup
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/magnusrodseth-git-branch-cleanup/badge)](https://www.skillsdirectory.com/skills/magnusrodseth-git-branch-cleanup)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: git-branch-cleanup
description: 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.
---

# 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
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

1. `git fetch --prune` (or pass `--fetch`) so `[gone]` detection is accurate.
2. Run the script; review the four buckets with the user.
3. 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).
4. Mention deletions are recoverable from reflog (`git reflog`) for ~30 days.

## Safety rules

- Prefer `git branch -d` (refuses unmerged work). Use `-D` **only** 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 `-d`
  will refuse them — that is expected; rely on the `gh`/`[gone]` signals instead.

## Manual fallback (no script / explaining the steps)

```bash
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), never
> `git branch | grep`. A naive `grep -v main` also strikes "**main**tenance",
> "do**main**", etc. Use exact, anchored matches.

Files in this skill

  • SKILL.md3.5 KB
  • scripts/cleanup-branches.sh5.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.