git branch --merged main | grep -vE '^\*|main|develop' | xargs -r git branch -d deletes every local branch already merged into main, except main and develop. It won't touch branches that were squash-merged: git never records their commits as merged, so --merged stays blind to them forever — check git branch -vv for the gone marker instead.
1. See what git considers merged first
$ clai show branches merged into main→ git branch --merged main feature-merged* main
This is a read-only list. The current branch is marked with an asterisk and can't be deleted — nor does it need to be.
2. Delete them, but protect your main branches
$ clai delete local branches merged into main, except main and develop→ git branch --merged main | grep -vE '^\*|main|develop' | xargs -r git branch -dDeleted branch feature-merged (was fb52fec).
-d is safe: git refuses to delete a branch with unmerged commits. The grep -vE filter is required, or main itself ends up in the list. -r keeps xargs from running at all when the list is empty.
3. Confirm nothing important got deleted
$ clai show remaining branches→ git branch feature-open* main
feature-open still has unmerged commits and stayed put. That's the check that the filter worked correctly.
4. The trap: squash merge hides from --merged
$ clai show branches whose upstream was deleted on the server→ git fetch --prune && git branch -vv | grep '\[.*: gone\]'
This is the part that matters. A squash merge never puts the branch's commits into main's history as-is, so --merged will never show it — it sits on your machine forever. But its upstream on the server is gone once the pull request merges, and git branch -vv flags that branch as gone. That's the reliable signal.
5. Delete branches with a vanished upstream
$ clai delete local branches whose source branch was deleted on the server→ git fetch --prune && git branch --format '%(refname:short) %(upstream:track)' | awk '$2 == "[gone]" {print $1}' | xargs -r git branch -D⚠ CAUTION — this is -D, not -d: it also deletes unmerged branches
-D is required precisely because git doesn't consider squash-merged branches merged, and refuses on -d. The price: read the list from the previous command before you run this one.
6. Clean up stale remote-tracking refs
$ clai remove references to branches that no longer exist on the server→ git remote prune origin
This only removes local origin/* references — it doesn't touch your branches. Handy when autocomplete keeps suggesting branches that were closed six months ago.
Gotchas
-dand-Dcarry different risk. Lowercase deletes only what's merged, uppercase deletes anything. Squash-merged branches need uppercase, so list first, delete second.--mergedwithout an argument counts from the current branch. Standing on a feature branch gives you a completely different list. Name the base explicitly:--merged main.- Deleting a local branch doesn't delete it on the server. That takes
git push origin --delete <name>. And the reverse: deleting it on the server doesn't remove your local copy.
Related questions
How do I recover a branch I deleted by accident? git reflog shows its last commit; then git branch <name> <hash>. Works until garbage collection runs.
Why won't a branch delete? Either you're standing on it — switch to main — or it has unmerged commits and -d is protecting you from losing them.
How do I see when a branch was last updated? git branch --sort=-committerdate -v sorts by freshness — useful for spotting abandoned ones.
See also
- Undo the last git commit but keep your changes
- Remove a secret from git history
- Git tasks you say out loud
CliAI turns the phrasing above into the exact command and shows it before running anything — the -D step gets a CAUTION label, not a silent run. Install it in one line.