Lorax
Prunes finished git worktrees under .claude/worktrees/, and refuses to touch work
that exists nowhere else.
Everything it needs ships in scripts/ next to this file. Below, $LORAX means
that directory.
As a member of the lorax plugin the SessionStart hook is already wired for you.
Standalone, run sh install.sh (next to this file) once to wire it, so every
session opens with a pass over the repo it starts in. That pass is
self-throttled to one PR check per hour per repo; --report here ignores the throttle.
sh install.sh --uninstall removes it. Both are idempotent and touch only
lorax's own entry in ~/.claude/settings.json.
The one criterion
Remove a worktree only when its work is recoverable without it. Not age, not cleanliness - recoverability. It removes only when the PR is MERGED, there are no tracked edits, and the last commit is over a day old. Capped at 10 per run. It never deletes a branch.
Ancestry lies here: squash-merged branch commits never become ancestors of the
default branch, so git branch --merged misses almost everything. Merged-PR state
from gh is the only truth, which is why it needs the network.
Verbs
Always run from inside the target repo, or aim it with --repo <dir>.
| The user wants | Run |
|---|---|
| See what would go, change nothing | sh $LORAX/lorax.sh --report |
| Actually prune | sh $LORAX/lorax.sh |
| Another checkout | sh $LORAX/lorax.sh --repo /path/to/repo --report |
| Animation on / off / status | sh $LORAX/lorax.sh --anim on | off | (no arg) |
Default to --report first. Show the user what it holds and why, and only run
the real prune if they confirm or clearly asked to prune outright.
Reading the output
One line. Would prune/Pruned names what is going, then every hold category with
its reason. The interesting half is what it refuses to cut:
HELD ... no merged PR- may hold commits on no other disk. Push before removing.Kept ... merged-but-dirty- tracked edits. Look before deleting.Kept ... open PR/closed unmerged- your call, never automatic.Not registered as worktrees- phantom directories git does not know about. Never auto-deleted; inspect by hand.
Relay these to the user rather than only the prune count - a held branch with unpushed commits is the one thing that actually needs a human.
Scope and safety
- Skips any repo with no
.claude/worktrees/directory. - Defers to a repo-local
.claude/hooks/lorax.shif one exists, so a repo with its own tuned copy is never pruned twice. - Exits silently with no
gh, nooriginremote, or no network - no PR truth means nothing safe to decide, so it holds everything. - Never runs from inside a worktree.
Animation
An opt-in truffula animation plays in a small native panel (macOS) when a run
actually removes something. Gated on ~/.claude/lorax-anim-on; off by default.
A --report never animates, because it cut nothing.
To demo it without waiting for a real prune, feed --anim-prep tab-separated
<1|0>\t<name>\t<reason> lines (1 = doomed) and open the result:
printf '1\tsome-branch\tPR #123 merged - work is in main\n0\tother\tPR still open\n' \
| sh $LORAX/lorax.sh --anim-prep $LORAX/lorax-anim.html /tmp/lorax-demo.html
sh $LORAX/lorax-open.sh /tmp/lorax-demo.html 220 130