Name
liminaltrees, lt — every git worktree on your machine, at once
Synopsis
liminaltrees
lt scan [--json] [--root dir]
lt gc [--dry-run] [--yes] [--root dir]
lt ui [--root dir]
Install
$ curl -fsSL https://lt.different.productions/install.sh | sh
macOS 15 or later. Universal binary, ~1 MB, no runtime dependencies. The script checks its own SHA-256 before unpacking, and you can read it first — it is served as plain text.
Description
git worktree list shows you one repository at a time, and tells you nothing about what any of the worktrees are for. So they accumulate — the ones you made, the ones your agents made and named after a hash, and the ones whose contents your OS quietly deleted without telling git.
liminaltrees finds all of them, works out what each one is, and removes the ones whose work has already landed. Anything holding work that exists nowhere else gets pushed somewhere safe first, and is only removed once that push is confirmed.
It sees through a squash merge, which git branch --merged cannot: the squashed commit has different parents and a different hash, so the original branch reads as unmerged forever. That is why worktrees for work you shipped weeks ago are still sitting there.
Commands
- liminaltrees
- Opens the dashboard. Piped, prints a table instead, so | grep still works.
- lt gc
- Shows you exactly what it would do, grouped by what it intends. Runs only if you type y.
- lt gc --dry-run
- Shows the plan and stops. Never prompts.
- lt scan --json
- For the agents that made this mess in the first place. Stable tokens, not prose.
How it decides
Checks run in order. The first one that matches decides, so the order is the whole safety policy.
- Detached, on a commit no upstream branch hasheld — nothing to rescue onto, and its registration holds the only ref
- Registered, but the directory is goneremove — bookkeeping only; the branch ref outlives it
- Anything wrote to it in the last hourheld — something is working in there
- Uncommitted filesrescue — those bytes exist in one place on earth
- Commits on no remote, and no patch upstreamrescue — still unique
- Its pull request merged or closedremove — this is the one that catches squash merges
- Every commit's patch is already on the default branchremove — it landed some other way
- Clean, pushed, unmergedyour call — safe to delete, but still somebody's plan
It will never
- touch a worktree something is writing to
- Anything written in the last hour is off limits, and that check outranks the rescue rules — pushing a half-finished state out from under a live agent run is worse than leaving the disk alone.
- delete work that exists nowhere else
- Uncommitted, unpushed, or reachable from no remote: it gets committed, pushed to a fresh wip/ branch, and confirmed on the remote. Only then is the worktree removed. A rescue that fails cancels that removal and nothing else.
- guess
- If it cannot reach the forge and cannot prove a branch landed, it says so and leaves the worktree alone. Not knowing is a result, not a reason to delete.
- act without showing you first
- Every removal is listed, with the single fact that decided it, before anything runs.
Files
Guarantee
A worktree is removed only when every commit in it is reachable from origin and the working tree is clean. After any run, git fsck reaches everything it reached before.
That is the point of the whole design: a plan you can approve without reading it closely. A cleanup tool you have to read closely every time is a cleanup tool you stop running.
Bugs
Rescue has no automated test coverage — exercising it means creating remote branches. Every classification rule is tested; the push-and-verify sequence has been read and run by hand, not by CI.
macOS only. It shells out to stat -f, which is the BSD spelling.
See also
git-worktree(1), git-cherry(1), and the twenty-three checkouts you forgot about