Skip to content

/sdlc:session-cleanup

Generated from solutions/ontological/plugin/plugins/sdlc/skills/session-cleanup/session-cleanup.md.

Tear down the branch and worktree a conversation worked in, but only after proving the work is safe: nothing uncommitted, nothing unpushed, and the branch tip reachable from the default branch. When any of those fail it reports exactly what is unlanded and stops without touching anything. When they pass it deletes the local and remote branch, removes the worktree, and prunes. Invoke at the end of a session for “clean up”, “clean up this branch”, “we’re done here”, “tear down the worktree”, or “is this safe to delete”.

  • Bash
  • Read

End-of-conversation teardown for the branch this session worked in. The whole skill is one question asked carefully — is every change on this branch already in the default branch? — followed by teardown only when the answer is yes.

Usage:

  • /sdlc:session-cleanup — clean up the current branch and, if the session is in one, its worktree.
  • /sdlc:session-cleanup task/2026-09-13-foo — clean up the named branch from elsewhere. Use this form when the session already left the worktree.
  • /sdlc:session-cleanup --dry-run — run every check and print the teardown commands without executing them.
  • /sdlc:session-cleanup --keep-branch — remove the worktree, leave both branches.

Deleting unlanded work is the failure that matters

Section titled “Deleting unlanded work is the failure that matters”

Every check below exists to prevent one outcome: destroying a change that exists nowhere else. A worktree left lying around costs disk. A deleted branch carrying the only copy of an hour’s work costs the work.

So the checks are not advisory. Any single failure stops the run, and the skill reports what is unlanded and where to find it rather than asking whether to proceed anyway. Re-run it after the user lands the work.

1. Resolve the branch, the worktree, and the main checkout

Section titled “1. Resolve the branch, the worktree, and the main checkout”

Establish four facts before checking anything:

Terminal window
git rev-parse --abbrev-ref HEAD # the branch under review
git rev-parse --show-toplevel # this checkout's root
git worktree list --porcelain # every worktree and its branch
git symbolic-ref --short refs/remotes/origin/HEAD # the default branch

The default branch falls back to main when origin/HEAD is unset. A branch argument overrides the current branch, and its worktree is the entry in git worktree list bound to it — never a path guessed from the branch name, since worktrees live wherever they were created.

Two branches are never cleaned up: the default branch itself, and a branch with no worktree and no commits of its own. Report SESSION-CLEANUP-NOTHING-TO-DO and stop.

Terminal window
git -C <worktree> status --porcelain

Any output at all — modified, staged, or untracked — fails the check. Report each path and stop with SESSION-CLEANUP-BLOCKED reason="uncommitted". Do not commit the changes on the user’s behalf; deciding what a stray edit means is their call.

Two separate facts, in this order:

Terminal window
git fetch origin --prune
git -C <worktree> log --oneline origin/<default>..<branch> # commits not in the default branch

An empty result means every commit on the branch is already reachable from the default branch — merged, squash-merged, or rebased in, all of which this test catches, because it asks about content reachability rather than about the PR.

When the result is non-empty the work is unlanded. Distinguish the two cases for the report, since the user’s next move differs:

  • An open or unmerged PR. gh pr view <branch> --json number,state,url finds it. Report the PR and its state — the work is landed once that PR merges.
  • No PR at all. The commits exist only on this branch, and possibly only on this machine (git log --oneline <branch> --not --remotes shows any that were never pushed). Say so plainly; this is the case where deleting would lose work.

Either way, stop with SESSION-CLEANUP-BLOCKED reason="unmerged" and list the commits. Change nothing.

Only reachable here. Run every command from the main checkout, not from the worktree being removed — a worktree cannot remove itself, and git worktree remove from inside it fails partway, leaving a registered worktree with a deleted directory.

Terminal window
git -C <main-checkout> worktree remove <worktree-path>
git -C <main-checkout> worktree prune
git -C <main-checkout> branch -d <branch>
git -C <main-checkout> push origin --delete <branch>

Four notes on the commands:

  • worktree remove without --force. It refuses on a dirty tree, which is a second backstop behind step 2. If it refuses, stop and report — do not reach for --force.
  • branch -d, never -D. The lowercase form refuses to delete an unmerged branch; after step 3 it should never refuse, and if it does, that disagreement means step 3 was wrong. Stop and report rather than overriding it.
  • The remote delete is skipped, not failed, when the branch is already gone from origin (a PR merged with auto-delete on). Mark it remote=absent.
  • --keep-branch runs the first two commands only.

Under --dry-run, print these four commands with the real paths substituted and exit with SESSION-CLEANUP-DRY-RUN ok, having mutated nothing.

Confirm the teardown actually happened rather than trusting the exit codes:

Terminal window
git -C <main-checkout> worktree list
git -C <main-checkout> branch --list <branch>
git ls-remote --heads origin <branch>

All three must come back without the branch or worktree. Then print one line:

SESSION-CLEANUP-DONE branch=<deleted|kept> worktree=<removed|absent> remote=<deleted|absent>

The other terminal markers are SESSION-CLEANUP-BLOCKED reason="<uncommitted|unmerged>", SESSION-CLEANUP-NOTHING-TO-DO, SESSION-CLEANUP-DRY-RUN ok, SESSION-CLEANUP-PARTIAL failed=<step> when teardown half-succeeded, and ERROR reason="..." when the branch or worktree could not be resolved.

  • A task’s branch belongs to /sdlc:task-close-out. That skill does everything this one does plus flipping the task file to closed/done with a completion note and archiving the lease. When the branch is task/<basename>, hand off to it rather than tearing down here and leaving the task entity stale.
  • The repo-wide sweep is /sdlc:project-cleanup. This skill looks at one branch — the one the conversation used. Use that one to find the accumulated orphans from sessions that ended without cleaning up.
  • Branch naming. See ${CLAUDE_PLUGIN_ROOT}/conventions/branch-naming.md — the prefix tells you whether a branch is task-owned and should be closed out instead.
  • Skill-authoring conventions. See ${CLAUDE_PLUGIN_ROOT}/skills/CLAUDE.md — this skill’s terminal markers are namespaced SESSION-CLEANUP-.
  • The skill never commits, pushes a change, or merges. Its only writes are the deletions in step 4.