Skip to content
Back to skills

Branch Cleanup

ASecurity

Prune stale git branches after a merge wave: delete closed/merged remote branches, remove local tracking branches that no longer exist on the remote, and optionally clean up local branches already merged to main. Use after running dependabot-triage, after a sprint wind-down, or when the user says "clean up branches", "prune branches", "delete merged branches", or "tidy up git".

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 14, 2026
ai-agentsgobashgit

Works with

  • claude code
  • cursor
  • cli

Security analysis

A100/100

Scanned October 14, 2026

npx -y skills add oleg-koval/agent-skills --skill branch-cleanup --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Branch Cleanup?

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

Security grade badge for Branch Cleanup
[![Security: A β€” Skills Directory](https://www.skillsdirectory.com/api/skills/oleg-koval-branch-cleanup/badge)](https://www.skillsdirectory.com/skills/oleg-koval-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: branch-cleanup
description: >
  Prune stale git branches after a merge wave: delete closed/merged remote branches,
  remove local tracking branches that no longer exist on the remote, and optionally
  clean up local branches already merged to main. Use after running dependabot-triage,
  after a sprint wind-down, or when the user says "clean up branches", "prune branches",
  "delete merged branches", or "tidy up git".
license: MIT
allowed-tools: Bash
compatibility: Codex, Claude Code, Cursor, GitHub Copilot, Windsurf, Kiro, and other Agent Skills compatible tools. Requires git and gh (GitHub CLI) authenticated.
metadata:
  targets: [_source-only]
  author: Oleg Koval
  tags:
    - git
    - branches
    - cleanup
    - maintenance
    - dependabot
    - github
source: weekly-pattern-learner
source_reason: "dependabot-triage was added to merge dependency PRs in bulk but leaves stale remote branches behind; no skill follows up to prune them"
source_date: "2026-08-10"
---
<!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->

> πŸ€– *Auto-generated by **weekly-pattern-learner** Β· dependabot-triage was added to merge dependency PRs in bulk but leaves stale remote branches behind; no skill follows up to prune them*

# Branch Cleanup

Remove stale remote and local branches safely after a merge wave. Defaults to
dry-run so you see exactly what will be deleted before anything is removed.

## Inputs

- **Remote** (optional, default: `origin`)
- **Base branch** (optional, default: `main`, detect from `gh repo view`)
- **--dry-run** (optional, default: on): preview deletions without executing them
- **--execute**: actually delete branches (requires explicit flag, safety default)
- **--include-local**: also clean up merged local branches (off by default)

## Step 1: Fetch and prune remote tracking references

Always start here. This removes local tracking refs for remote branches that are
already gone, without touching anything else:

```bash
REMOTE="${Remote:-origin}"
git fetch --prune "$REMOTE"
```

After pruning, print the count of removed tracking refs:

```bash
git branch -vv | grep ': gone]' | wc -l
```

## Step 2: Identify stale remote branches

Find remote branches whose associated PR is merged or closed:

```bash
# Get all non-default remote branches
BASE=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name' 2>/dev/null || echo "main")

git branch -r --merged "$REMOTE/$BASE" \
  | grep -v "HEAD" \
  | grep -v "$REMOTE/$BASE" \
  | sed "s|$REMOTE/||" \
  | sort
```

Cross-reference with GitHub to confirm each branch's PR state. Only include branches
with a confirmed merged or closed PR. An empty result means the branch is not eligible:

```bash
STALE_BRANCHES=()
for branch in $(git branch -r --merged "$REMOTE/$BASE" \
    | grep -v HEAD | grep -v "$REMOTE/$BASE" | sed "s|$REMOTE/||"); do
  PR_DATA=$(gh pr list --head "$branch" --state merged \
    --json number,title,mergedAt \
    --jq '.[0] | "\(.number) merged \(.mergedAt | .[0:10])"' 2>/dev/null)
  if [ -z "$PR_DATA" ]; then
    PR_DATA=$(gh pr list --head "$branch" --state closed \
      --json number,title,closedAt \
      --jq '.[0] | "\(.number) closed \(.closedAt | .[0:10])"' 2>/dev/null)
  fi
  if [ -n "$PR_DATA" ]; then
    echo "$branch  ←  $PR_DATA"
    STALE_BRANCHES+=("$branch")
  else
    echo "$branch  ←  no confirmed PR (skipped)"
  fi
done
```

This produces a table like:

```
dependabot/npm_and_yarn/lodash-4.17.21   ←  42 merged 2026-07-31
dependabot/npm_and_yarn/eslint-8.57.0    ←  43 merged 2026-07-31
feature/login-refresh                    ←  38 merged 2026-07-28
```

## Step 3: Safety filter

**Never propose deletion of:**

| Branch | Reason |
|--------|--------|
| `main`, `master`, `develop` | Protected base branches |
| `beta`, `staging`, `production` | Release/deploy targets |
| Any branch with an open PR | May still be in review |
| Any branch not yet merged to base | Would lose commits |
| Branches the user has modified locally in the last 24h | Work-in-progress |

Check for open PRs before including a branch in the deletion list:

```bash
OPEN=$(gh pr list --head "$branch" --state open --json number --jq 'length')
[ "$OPEN" -gt 0 ] && echo "SKIP (open PR): $branch"
```

## Step 4: Preview or execute

### Dry-run (default)

Print the deletion plan and stop. Never touch the remote without `--execute`:

```
Branch cleanup preview, dry-run (pass --execute to apply)

  Remote branches to delete (all merged to main):
    origin/dependabot/npm_and_yarn/lodash-4.17.21     (PR #42, merged 2026-07-31)
    origin/dependabot/npm_and_yarn/eslint-8.57.0      (PR #43, merged 2026-07-31)
    origin/feature/login-refresh                       (PR #38, merged 2026-07-28)

  Local tracking refs already pruned: 3 (by git fetch --prune)

  Would NOT delete:
    origin/beta            (protected release branch)
    origin/feature/signup  (open PR #47)

  Run with --execute to apply.
```

### Execute

```bash
# Delete remote branches
for branch in "${STALE_BRANCHES[@]}"; do
  git push "$REMOTE" --delete "$branch" && echo "deleted: $REMOTE/$branch"
done
```

## Step 5: Local branch cleanup (optional, --include-local)

Only runs when `--include-local` is explicitly passed.

Find local branches already merged to base and not checked out:

```bash
git branch --merged "$BASE" \
  | grep -v "^\*" \
  | grep -v "$BASE" \
  | grep -v "main\|master\|develop\|beta\|staging"
```

For each candidate, confirm there is no uncommitted work and the tracking ref is
gone before deleting:

```bash
git branch -vv "$branch"   # should show ': gone]' if remote is deleted
git branch -d "$branch"    # safe delete: fails if unmerged work exists
```

Never use `-D` (force delete): let `-d` protect you from accidental data loss.

## Report

```
Branch cleanup complete

  Remote:
    Deleted:   12 branches (10 Dependabot, 2 feature branches, all merged)
    Protected: 2 skipped (beta, open PR)

  Local tracking refs:
    Pruned:    12 stale refs (git fetch --prune)

  Local branches (--include-local):
    Deleted:   3 branches already merged to main
    Kept:      1 branch (has uncommitted work)

  Repo now has: N remote branches, M local branches
```

## Chaining

| Before this skill | After this skill |
|---|---|
| `olko:dependabot-triage` (merged patch PRs) | next `olko:morning-routine` starts clean |
| Sprint wind-down / milestone close | `git fetch` confirms clean state |
| Any large batch merge | nothing: maintenance complete |

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.