Task: Add compass command to prune merged git branches and report branch/environment usage
Table of Contents
This page documents a task in the Compass quality-of-life improvements: remaining follow-ups story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Give developers a single compass command to safely prune git branches that have already been merged into origin/main – both local and remote – without risking deletion of a branch another worktree/environment is actively using, and to surface a report of branch staleness and fleet ownership so cleanup decisions (or further manual deletions) can be made confidently.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Compass quality-of-life improvements: remaining follow-ups |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-29 |
Acceptance
- A new compass command (git/branches subcommand) identifies local branches fully merged into origin/main and remote branches fully merged into origin/main, excluding the current branch, main itself, and any branch checked out by another worktree (cross-referenced via 'compass fleet' / 'git worktree list' equivalent).
- The command deletes the safe-to-delete set both locally ('git branch -d') and on the remote ('git push origin –delete'), in one step, with a summary of what was deleted.
- The command (or a companion report mode) lists branches NOT deleted, bucketed by reason: checked out by another environment (name the environment/worktree), unmerged/active work, or ambiguous/needs-human-review.
- The report flags 'stale' branches – e.g. no commits in N days, or merged-but-still-present-in-fleet – so a human can decide on cases the automatic prune correctly leaves alone (e.g. a worktree still parked on an already-merged branch after finishing its task).
- Documented in the relevant recipe (doc/recipes/compass/…) with example output.
- Manually exercised against the current live branch set (the same one audited in this session: ~28 local, ~28 remote branches) and confirmed it reaches the same safe-delete conclusions reached manually (6 local + 6 remote merged branches deleted, feature/write-up-wire-format-architecture and the then-current feature branch correctly left alone).
Plan
Added a new self-contained compass branches command
(src/compass_branches.py, dispatched from compass.py alongside
the other lazily-imported command modules like compass_services):
compass branches/compass branches report— read-only report.git fetch --prune origin, then classify every local (git for-each-ref refs/heads) and remote (git for-each-ref refs/remotes/origin, strippingorigin/and skipping theorigin/HEADsymref) branch againstgit branch --merged origin/main/git branch -r --merged origin/main, and against the live fleet (git worktree list --porcelain, same sourcecompass fleetuses). Buckets: safe to delete, or kept with a reason (current,main,fleet:<worktree>,unmerged).compass branches prune [-y]— dry-run by default (prints what would be deleted); with-y, deletes the safe set local (git branch -d, not-D, so unmerged-commit branches are refused even if the--mergedcomputation were ever wrong) then remote (git push origin --delete), then reprints what's left alone and why.
Manually exercised against the live 43-branch set (see Result) — matched the manual workflow's expected outcome.
Notes
Reference session: manual steps performed 2026-07-29
The session that captured this task ran the following sequence by
hand, in the clever_dijkstra worktree, right after merging PR #1719.
This is the workflow to formalise into a compass command.
Fetch and prune remote-tracking refs first, so deleted remotes don't show up as phantom candidates:
git fetch --prune origin
List local branches with tracking/verbose info, and remote branches, to see the full candidate set:
git branch -vv git branch -r
On this run: ~28 local branches, ~28 remote branches. Local branches checked out by other worktrees are marked with a leading
+bygit branch -vv(not deletable from this worktree without first switching that worktree off it); the worktree's path is printed after the commit hash, e.g.(/mnt/development/OreStudio/ores_dev_jolly_knuth).Compute the merged set against
origin/main(not localmain, which can be stale in a worktree that isn't actively tracking it):git branch --merged origin/main git branch -r --merged origin/main
--mergedagainst localmainunder-reports if that worktree's localmainis behind (seen here: localmainwas 9 commits behindorigin/main, tracked in a different worktree entirely).Cross-reference the merged set against the live fleet before deleting anything:
compass fleet
Exclude from deletion:
- the current branch (can't delete the branch you're on),
mainitself,- any branch
git branch -vvmarks with+(checked out in another worktree) or thatcompass fleetshows as another environment's activeBRANCHcolumn, - any branch merged into
origin/mainbut still parked as another environment's current branch even though its task/PR already closed (e.g.feature/write-up-wire-format-architecturewas fully merged but still checked out bysolid_dirac– correctly left alone rather than force-cleaned, since another session may still be using that worktree state).
Delete the safe set, local then remote, as two explicit batches (not a loop with individual confirmations – one command per side listing every branch to delete):
git branch -d <branch1> <branch2> ... git push origin --delete <branch1> <branch2> ...
-d(not-D) deliberately – it refuses to delete a branch with unmerged commits, which is the safety net that matters if the--mergedcomputation above was wrong for any reason.- Report back to the user: what was deleted (local count + remote count, names), and what was deliberately left alone and why (bucketed: "checked out by another environment", "is the current branch", "is main", "unmerged/active work").
Concrete before/after from that session
- Started with ~28 local / ~28 remote branches.
- Deleted 6 local + 6 remote (one extra remote-only deletion,
claude/windows-cxx-linking-hotfix-upn21a, which had no corresponding local branch to check but was independently confirmed viagit branch -r --merged origin/main). - Left alone:
feature/write-up-wire-format-architecture(merged, but checked out bysolid_dirac),feature/codegen-qt-parent-scoped-list(was the current branch at the time; deleted locally in a later step once the worktree moved off it onto the next task's branch),main(checked out by theores.gitworktree), and every branch representing another environment's still-open, unmerged work. - This asymmetry – a branch fully merged into main but still the active branch of a live worktree – is exactly the "stale but fleet-owned" case the acceptance criteria call out; the command should report it, not silently delete or silently ignore it.
Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
PRs
| PR | Title |
|---|---|
| #1866 | [compass] Add compass branches: report/prune git branches merged into origin/main |
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Batch git branch -d / push –delete return non-zero on partial failure but code treated whole batch as undeleted ("0 deleted") | compass_branches.py | Fixed | Recompute deleted set from before/after local_branches()/remote_branches() diff instead of trusting the batch return code |
| 1 | fleet_active_branches doesn't check subprocess returncode, unlike the _git() helper used elsewhere | compass_branches.py | Fixed | Added explicit returncode check, consistent with _git() |
| 1 | No dedicated unit tests for classify() | compass_branches.py | Declined | Matches existing convention – no other compass_*.py command module has dedicated tests either; covered by manual exercise against the live branch set |
| 1 | fetch_prune failure is non-fatal for prune -y, could work off stale origin/main | compass_branches.py | Declined | Judgment call flagged by reviewer, not a bug; git branch -d's own safety net (refuses unmerged branches) still applies even with a stale view |
| 1 | _MAIN_BRANCHES includes "master", dead weight since merge-base is hardcoded to origin/main | compass_branches.py | Declined | Harmless; kept for defence-in-depth in case a checkout ever has a stray master branch |
Result
Added compass branches (report/prune) and its recipe
(How do I prune merged git branches with compass?). Manually
exercised against the live branch set in this worktree: report
correctly bucketed 42 safe-to-delete branches (41 local + 1
remote-only, bot/nightly-format) against 32 kept branches (9
fleet-owned across other worktrees, main local+remote, 21
unmerged/active); prune -y deleted exactly that safe set (git
branch -d locally, git push origin --delete for the remote) and
left every fleet-owned/unmerged/current/main branch untouched,
matching the reference session's expected asymmetry (merged-but-
fleet-owned branches are reported, not force-deleted). All acceptance
criteria met.