If you run multi-session agent fleets, you know this smell: git worktree list
scrolling past checkouts nobody owns anymore. A session died, its worktree
leaked, and now there are six stale branches with names like fix-thing-3
that nobody dares to delete. Multiply by every fleet run and you get a repo
full of ghost worktrees.

Ben Newton's worktree claim guard (a session-fleet Mod) proved this is a real
problem class: sessions racing over the same worktrees. Guards solve it at
creation time by intercepting and blocking. worktree-janitor solves it
the other way: after the fact, with a CLI that scans, classifies, and prunes
what is verifiably dead. Janitor, not bouncer. The two compose -- guard at
creation, janitor on a cron or after a fleet shuts down.

How it works

Each session claims the worktree it creates:

wtj claim ./my-wt --session-id fleet-run-42 wtj heartbeat ./my-wt # refresh while you work 

The claim is a small JSON file in the worktree's git admin dir recording the
session id, owner PID, and a heartbeat timestamp. A claim is dead when
the PID no longer exists, or (no PID recorded) the heartbeat is older than
--stale-after minutes (default 30).

Then:

wtj list # worktrees + claim / dirty / merged status wtj prune --dry-run # preview wtj prune --yes # remove dead-claim worktrees 

The safety model

The hard rule, enforced in code, not docs: a worktree with uncommitted
changes is never pruned
, no matter how dead its claim is. It gets skipped
with a warning. Also never touched: the main worktree, bare repos, locked
worktrees, active claims, and unclaimed worktrees (unless you pass
--include-unclaimed). Deletion requires --yes; the default is report-only.

Try it

pip install worktree-janitor 

Stdlib-only, no dependencies -- it should run in any agent sandbox without a
network install. 12 smoke tests ship with it. It's the sibling of
routine-sweeper: same session-hygiene family, but for git worktrees instead
of routine files.