| name | prune-merged |
| description | Clean up local branches that have been merged into main. |
| allowed-tools | ["Read","Bash","AskUserQuestion"] |
Prune Merged Branches
Remove local branches that have already been merged into remote main.
Usage
/git:prune-merged - List and prune merged branches (with confirmation)
/git:prune-merged --dry-run - Only list branches that would be pruned
Instructions
-
Fetch latest from remote:
git fetch origin --prune
-
Find merged branches (store as MERGED_BRANCHES):
Detect branches merged via two methods:
Method A - Regular merges:
git branch --merged origin/main | grep -v '^\*' | grep -v 'main' | grep -v 'master'
Method B - Squash merges (remote branch deleted after PR merge):
git branch -vv | grep ': gone]' | sed 's/^[+* ] *//' | awk '{print $1}'
The sed strips the leading + (worktree-checked-out) or * (current branch) marker that git branch -vv puts in column 1. Without it, awk '{print $1}' returns the marker as a phantom branch name and drops the real one.
Combine both lists, removing duplicates. This catches:
- Branches with commits directly in origin/main history (regular merge)
- Branches whose remote tracking branch was deleted after merge (squash merge)
Bucket worktree-attached separately. Lines starting with + in git branch -vv
are checked out in another worktree and cannot be deleted with git branch -D until
detached. Capture them so step 4 can list them as "blocked by worktree" rather than
attempting (and failing) to delete them, and so the operator sees what to detach.
-
Handle dry-run mode:
- If
$ARGUMENTS contains --dry-run:
- Display the list of branches that would be pruned
- Exit without deleting
-
Display branches for confirmation:
-
If MERGED_BRANCHES is empty:
No merged branches to prune. All local branches have unmerged commits.
Exit here.
-
Otherwise, display:
Found merged branches to prune:
| Branch | Last Commit |
|--------|-------------|
| feature/auth | 3 days ago |
| fix/login-bug | 1 week ago |
-
If any worktree-attached merged branches were captured in step 2, list them
in a separate "Blocked by worktree" table with the worktree path so the
operator can detach them and re-run:
Blocked by worktree (skipped — detach with `git worktree remove <path>`):
| Branch | Worktree Path |
|--------|---------------|
| sweep/137-foo | tmp/.../worktrees/issue-137 |
-
Use AskUserQuestion to confirm:
- "Delete all" - Delete all listed branches
- "Select branches" - Let the operator choose which to delete
- "Cancel" - Exit without deleting
-
Delete confirmed branches:
For branches detected via Method A (regular merge):
git branch -d <branch-name>
For branches detected via Method B (squash merge / gone remote):
git branch -D <branch-name>
Note: Method A branches use -d as a safety check. Method B branches
require -D because their commits aren't in main's history (they were
squashed). The "gone" remote is the indicator that the PR was merged.
-
Report results:
Pruned branches:
- feature/auth
- fix/login-bug
Kept branches:
- (none)
Remaining local branches: 3
Example Output
$ /git:prune-merged
Fetching latest from origin...
Found merged branches to prune:
| Branch | Last Commit |
|--------|-------------|
| docs/update-readme | 2 days ago |
| feature/add-auth | 1 week ago |
| fix/typo | 3 weeks ago |
? Delete these branches?
> Delete all
Select branches
Cancel
Pruned branches:
- docs/update-readme
- feature/add-auth
- fix/typo
Remaining local branches: 2 (main, feature/in-progress)
Important Notes
- Squash merge support: Detects branches whose remote was deleted after PR merge, even if squash-merged
- Safe deletion: Regular merges use
-d; squash merges use -D (safe because remote deletion confirms merge)
- Protected branches: Never deletes
main, master, or the current branch
- Remote cleanup: Step 1 already prunes stale remote-tracking references via
git fetch origin --prune
- Worktrees: Branches in use by worktrees cannot be deleted; detach the worktree first
Known Edge Cases
Branch tracking origin/main instead of its own remote
Symptom: A squash-merged branch isn't detected even after git fetch --prune.
Cause: The branch was configured to track origin/main directly instead of origin/<branch-name>. When checking git branch -vv, it shows [origin/main: ahead X] instead of [origin/<branch-name>: gone].
Detection: Method B (git branch -vv | grep ': gone]') fails because origin/main still exists.
Resolution: These branches require manual identification. Check git branch -vv for branches showing [origin/main: ahead X] where the remote branch no longer exists:
git branch -r | grep "<branch-name>"
Example: Branch feature/user-settings tracked origin/main instead of origin/feature/user-settings. After the PR was squash-merged and the remote deleted, it showed [origin/main: ahead 3] instead of : gone].