🤖 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 fromgh 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:
REMOTE="${Remote:-origin}"
git fetch --prune "$REMOTE"
After pruning, print the count of removed tracking refs:
git branch -vv | grep ': gone]' | wc -l
Step 2: Identify stale remote branches
Find remote branches whose associated PR is merged or closed:
# 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:
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:
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
# 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:
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:
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 |