Cross-runtime: follow runtime compatibility for invocation, delegation, configuration precedence, state paths, and permissions.
You are cleaning up local Git branches whose remote tracking branch has been deleted (shown as [gone] by Git), along with any worktrees checked out to them. This is destructive, so it is designed to be safe by default: it never discards uncommitted work or unmerged commits without explicit, informed confirmation. Follow each step in order.
Step 1: Parse Arguments
--dry-run→ show exactly what would be removed and stop. In dry-run, make no changes to Git state at all (no fetch/prune, no deletion).
Default (no args): proceed through confirmation (Step 4) before any deletion.
Step 2: Refresh Remote State (skip on --dry-run)
Only when NOT --dry-run, prune stale remote-tracking refs so [gone] is accurate:
git fetch --prune
- On
--dry-run, do NOT run this (it mutates local refs). Use the current tracking state and note in the output that it may be stale — the user can re-run without--dry-runto refresh. - If
fetchfails (offline, no remote), warn and continue with the current state.
Step 3: Identify Gone Branches
List local branches whose upstream is marked [gone]:
git for-each-ref --format '%(refname:short) %(upstream:track)' refs/heads \
| awk '$2 == "[gone]" { print $1 }'
Resolve the protected branches (never delete these) from .git-workflow/config.yaml, then .claude/config.yaml as a legacy read-only fallback, then defaults:
workflow.developmentBranch— defaultstagingworkflow.productionBranch— defaultmain- Also always protect: the current branch (
git branch --show-current), andmaster.
Exclude every protected branch from the deletion list even if it is marked [gone] (a gone staging/main is a config/remote quirk, not a signal to delete it).
Step 4: Assess Safety and Confirm
For each candidate branch, gather safety signals to show the user:
- Merged? Whether the branch's commits are already integrated (see Step 6 — prefer
git branch -d, which refuses unmerged branches). - Attached worktree? And whether that worktree is dirty (see Step 5).
Present a table: branch → merged/unmerged → worktree path (clean/dirty). Then:
- On
--dry-run: print the table and stop (no changes). - Otherwise, ask the user to confirm. Call out any unmerged branches and any dirty worktrees explicitly — these lose data if removed. Default to NOT removing those unless the user explicitly opts in.
Step 5: Remove Associated Worktrees (safely)
A branch checked out in a linked worktree cannot be deleted until the worktree is removed. Map worktrees to branches:
git worktree list --porcelain
For each gone branch with a worktree, check for uncommitted work first:
git -C "{worktree_path}" status --porcelain --untracked-files=all
- If the output is empty (clean): remove it with
git worktree remove "{worktree_path}"(no--forceneeded for a clean worktree). - If non-empty (dirty/untracked files): do NOT remove it unless the user explicitly confirmed discarding its contents in Step 4. Only then use
git worktree remove --force "{worktree_path}". Otherwise skip the branch and report why.
Then prune administrative leftovers: git worktree prune.
Step 6: Delete the Branches (merged-safe)
For each confirmed branch, try the safe delete first:
git branch -d "{branch_name}"
-dsucceeds only if the branch is already merged (into HEAD or its upstream). This is the desired path for the common "PR merged, remote branch deleted" case.If
-dfails because the branch is not merged, the branch has local commits that exist nowhere else. Show them:git log --oneline "{branch_name}" --not --remotesReport these commits and ask the user for explicit confirmation before force-deleting. Only on explicit confirmation:
git branch -D "{branch_name}"
Never force-delete (-D) unmerged branches without showing the commits and getting a clear yes.
Step 7: Report
Print a summary:
Pruned gone branches:
✓ feature/eng-123-old-thing (merged; worktree removed: ../wt-eng-123)
✓ fix/proj-456-done (merged)
⚠ spike/experiment (skipped: 3 unmerged commits — not deleted)
⚠ feature/wip (skipped: worktree has uncommitted changes)
Removed: 2 branches, 1 worktree. Skipped: 2.
If nothing was gone, say so plainly: "No gone branches to clean up."
Error Handling
| Scenario | Action |
|---|---|
git fetch fails |
Warn, continue with current tracking state |
| Branch is protected (current/dev/prod/master) | Skip it, note it is protected |
| Branch is unmerged | Skip by default; only force-delete after showing commits + explicit confirmation |
| Worktree is dirty | Skip by default; only --force remove after explicit confirmation |
| Worktree path is missing/locked | Report the error, skip that branch, continue |
git branch -d/-D fails |
Report and continue with the rest |