Agent Skills: Reap Worktrees

Inspect and reclaim leftover /start wt worktrees. Shows which worktrees under .claude/worktrees/ are eligible for cleanup (PR merged, branch merged, or Linear issue Canceled/Done) and which are preserved (active or abandoned-for-resumption), and can reap the eligible ones now. Use when the user says 'reap worktrees', 'clean up worktrees', 'what worktrees are leftover', 'prune worktrees', or invokes /reap-worktrees.

UncategorizedID: alienfast/claude/reap-worktrees

Install this agent skill to your local

pnpm dlx add-skill https://github.com/alienfast/claude/tree/HEAD/skills/reap-worktrees

Skill Files

Browse the full folder contents for reap-worktrees.

Download Skill

Loading file tree…

skills/reap-worktrees/SKILL.md

Skill Metadata

Name
reap-worktrees
Description
Inspect and reclaim leftover /start wt worktrees. Shows which worktrees under .claude/worktrees/ are eligible for cleanup (PR merged, branch merged, or Linear issue Canceled/Done) and which are preserved (active or abandoned-for-resumption), and can reap the eligible ones now. Use when the user says 'reap worktrees', 'clean up worktrees', 'what worktrees are leftover', 'prune worktrees', or invokes /reap-worktrees.

Reap Worktrees

/start wt creates a worktree at <repo>/.claude/worktrees/<issue-lower>. Two flows leave one behind that nothing else reclaims:

  1. /finish pr (worktree mode) — the PR merges asynchronously on GitHub later, so /finish can't clean up when it runs. The SHIPPED-PR tag tells you to remove the worktree after the PR lands, but that hand-off is manual and easy to forget.
  2. An issue Canceled/Done directly in Linear with no live /start session — /start Step 8.5 only surfaces cleanup while a session is running, so a cancel outside that window orphans the worktree.

(/finish merge does not leak — finish-merge.sh removes the worktree on a successful merge.)

A local launchd job (reap-worktrees-cron.shcom.alienfast.worktree-reap, hourly) runs the reaper automatically, mirroring the merge-queue drainer. This skill is for on-demand inspection and cleanup between those passes.

Reap discipline

reap-worktrees.sh destroys a worktree only on positive evidence of completion — never on mere inactivity. A worktree is reaped iff all hold:

  • Completion evidence (any one): its branch is an ancestor of its source branch or the repo default (merged); or its PR state is MERGED (via gh); or its Linear issue state type is terminal (completed/canceled/duplicate).
  • No unsaved commits: every commit on the branch is reachable from a durable ref — merged into mainline, or present on its origin remote-tracking branch (pushed).
  • Clean working tree: git status --porcelain is empty. The reaper never passes --force, so untracked work is never destroyed; gitignored scratch (tmp/, node_modules) doesn't block removal.
  • No in-flight deferred merge: no <repo>/.claude/merge-queue/<issue>.json marker (the drainer owns those).
  • Not live: the worktree's index is stale (no git activity for WORKTREE_REAP_GRACE_MIN minutes, default 60), and the branch has commits beyond its recorded baseline — or, for a zero-commit branch, the completion evidence is something other than "merged". A zero-commit branch is trivially an ancestor of its source, so the merged test says nothing about it (reaping on that alone destroyed a live just-forked worktree once — PL-459). A terminal Linear issue is independent of commit count and does count, which is what reclaims a /start wt worktree whose issue was canceled before the first commit. So is a dead or released owning session (wt_owner_alive, from the /start identity stamp): a zero-commit worktree whose session provably died, or that was released via wt-disown.sh, is abandoned no matter what its issue says, and would otherwise be preserved forever. alive and unknown never reap — an unresolvable owner has to fail safe, and in a claude agents fleet every session shares the fleet-root pid, so a session that dies while its root runs still reads alive and keeps its worktree until the root exits.

Abandoned-for-resumption worktrees are preserved automatically — branch unmerged, PR open, issue still active means they fail the evidence test, so no special-casing is needed. A worktree that is eligible but dirty or has local-only commits is reported, not reaped, with the exact command to finish the job by hand.

Source branch comes from the per-worktree start.source-branch config recorded by start-wt-setup.sh; the repo set is the union of the self-registering ~/.claude/worktree-repos.txt and ~/.claude/merge-queue-repos.txt.

The gates are regression-guarded by reap-worktrees.test.sh (bash ~/.claude/scripts/reap-worktrees.test.sh) — run it after any change to them, and add the case alongside the fix. This script deletes work; an unguarded gate is one that quietly reopens.

Orphan host processes

Every teardown path is git-only, so the dev servers, watchers, and job runners a worktree started keep running after it is removed — and its pidfiles went with the directory, leaving the resolved cwd as the only handle. So each reap pass also sweeps processes (yours only) whose cwd sits under <repo>/.claude/worktrees/<name> where that <name> directory no longer exists on disk. That gate is what makes the kill safe: a live worktree, or a sibling of a dead one, can never be selected. list prints ORPHAN-PROC pid=… cwd=… and kills nothing; reap sends TERM, then KILL to survivors, logging REAPED-PROC pid=… cwd=…. Process names are deliberately not matched — puma and sidekiq rewrite their proctitle, so a pkill -f pass misses real orphans and can hit unrelated processes. Without lsof the sweep notes itself and skips.

Usage

Inspect (dry run — mutates nothing, takes no lock):

~/.claude/scripts/reap-worktrees.sh list            # every registered repo
~/.claude/scripts/reap-worktrees.sh list <repo>     # one repo

Each worktree prints one of: REAP-ELIGIBLE, KEEP (with the reason — active, unpushed, or dirty), SKIP (detached / merge-queued), or STRAY, followed by an ORPHAN-PROC line per leftover host process.

Reap (mutating — removes eligible worktrees, serialized per repo under the same common-git-dir lock /finish merge uses, so it can never race an in-flight merge):

~/.claude/scripts/reap-worktrees.sh reap            # every registered repo
~/.claude/scripts/reap-worktrees.sh reap <repo>     # one repo

When the user asks to inspect, run list and summarize the verdicts. When they ask to clean up, run list first, show what will be removed, and on confirmation run reap. The hourly launchd log is at ~/.claude/logs/worktree-reap.log.

The launchd agent

~/.claude/update.sh installs and refreshes the agent automatically (renders the __HOME__ template and bootstraps it idempotently — see the plist header for the by-hand commands). To remove it:

launchctl bootout gui/$(id -u)/com.alienfast.worktree-reap
rm ~/Library/LaunchAgents/com.alienfast.worktree-reap.plist