Full Branch Cleanup and Worktree Hygiene
Use when the user asks to:
- track all untracked files/changes
- merge work to origin/main
- merge all branches to
main - clean stale local/remote branches and worktrees
This is a high-side-effect workflow. It is appropriate only when the user explicitly authorizes broad branch cleanup.
Core sequence
Start from a live inventory:
git status --short --branch git branch -vv git branch -r git worktree list --porcelain git ls-remote --heads originTrack dirty changes before branch deletion.
- Commit root dirty/untracked files in a broad sync commit if the user asked to track all changes.
- If a dirty path is itself a nested git repo/worktree/submodule, inspect it separately first.
Preserve nested gitlinks correctly.
- Check whether a nested path is tracked as a gitlink:
git ls-files -s <path> - Mode
160000means the parent repo tracks a gitlink. Do not delete it as generic stale-worktree cleanup. - Commit inside the nested repo first:
git -C <nested> add -A git -C <nested> commit -m "..." - Then stage/commit the parent gitlink update.
- Check whether a nested path is tracked as a gitlink:
Merge current integration/feature branches to
main.- Switch to
mainand fast-forward from origin first:git checkout main git pull --ff-only origin main - Identify branches that still contain unique commits:
The right-side count isgit rev-list --left-right --count main...<branch>branch_only; merge branches withbranch_only > 0. - For remote-only branches, use
origin/<branch>in the same check.
- Switch to
Resolve stale-branch conflicts conservatively.
- For stale branches that conflict in planning indexes or duplicated artifacts, prefer current
mainfor overlapping/conflicted files while accepting non-conflicting branch files:git checkout --ours -- <conflicted-file> git add <conflicted-file> git commit -m "Merge branch '<branch>' into main" - If a non-conflicting branch change triggers a security/content hook, drop just that file back to current
mainand record the reason in a handoff. Preserve unrelated review evidence/artifacts where safe. - Avoid bypassing hooks during cleanup unless explicitly approved and documented.
- For stale branches that conflict in planning indexes or duplicated artifacts, prefer current
Push and verify remote state.
- Push
main. - GitHub may print
cannot lock ref ... is at <new> but expected <old>even when the remote ref advanced. Always verify before retrying:git ls-remote --heads origin main git status --short --branch
- Push
Delete merged remote branches.
- Only delete a remote branch if it is an ancestor of
main:git merge-base --is-ancestor origin/<branch> main git push origin --delete <branch> - Then prune and verify:
git fetch --all --prune git branch -r
- Only delete a remote branch if it is an ancestor of
Remove stale worktrees before deleting local branches.
- Worktrees block branch deletion.
- Remove stale worktrees with
git worktree remove -f <path>. - Retain intentional tracked gitlinks/nested repos unless separately approved.
- If there are dozens of worktrees, the removal loop may exceed tool/terminal timeouts. Treat timeout as partial progress: re-run
git worktree list --porcelain, continue removing the remaining paths, thengit worktree prune. - If
git worktree removereports a missing.gitfile for a stale path, rungit worktree pruneand re-check before attempting manual filesystem cleanup. - Run
git worktree pruneafterward.
Delete merged local branches.
current=$(git branch --show-current) for b in $(git for-each-ref --format='%(refname:short)' refs/heads); do [ "$b" = "$current" ] && continue if git merge-base --is-ancestor "$b" main; then git branch -D "$b" fi doneWrite a cleanup handoff and commit it. Include final branch/worktree state, retained gitlinks, merged branches, deleted remote branches, conflict-resolution choices, and next-session notes.
Final verification checklist
Expected full-cleanup end-state:
git status --short --branch # clean on main
git branch -a # only main + origin/main
git worktree list # main + intentional retained gitlinks only
git ls-remote --heads origin # only refs/heads/main unless protected branches intentionally remain
Also verify no local branch has unique commits left:
python - <<'PY'
import subprocess
for b in subprocess.check_output(['git','for-each-ref','--format=%(refname:short)','refs/heads'], text=True).splitlines():
if b == 'main':
continue
ahead = subprocess.check_output(['git','rev-list','--right-only','--count',f'main...{b}'], text=True).strip()
print(b, ahead)
PY
Pitfalls
- Do not remove a tracked gitlink just because
git worktree listshows it as a detached worktree. - Do not trust a failed-looking push if the error is
cannot lock ref; verify withgit ls-remote. - Do not delete unmerged remote branches without proving ancestry into
main. - Do not let stale branch conflicts overwrite current planning indexes with older statuses.
- Do not normalize hook bypass during cleanup; prefer resolving or dropping the offending stale file.
- Auto-sync/background processes can create new dirty provider scorecards, planning markers, or handoffs immediately after a clean commit/push. Re-run
git status --short --branchafter every push/merge wave; if the user explicitly asked to track all changes, commit the new dirt too before declaring the checkout clean. - Another terminal may approve an issue during cleanup, creating approval markers and plan/index updates while your handoff is being written. Before exit, revalidate live GitHub labels plus local
.planning/plan-approved/<issue>.md, plan header, anddocs/plans/README.md, then either commit the approval-state sync or document that it is already committed.