the worktree-lifecycle substrate for parallel coding agents
never loses work · never reaps what's in use · the registry, not the filesystem, is truth
Every vendor ships worktree creation now — claude --worktree, the Claude
Agent SDK's isolation: worktree, Cursor, Copilot CLI — and every one of them
stops there. Nobody owns the rest of the life: the branch still alive after
the pane died, the checkout nobody is sitting in, the tree with 40
uncommitted minutes in it, the branch whose PR merged yesterday and has
kept committing since.
If you've ever git worktree list'd through a graveyard trying to remember
which of those you can safely delete — that's the problem scruff makes go away.
cd "$(scruff new fix-flaky-test)" # a lane on this repo: new checkout, new branch, path on stdout
scruff # every lane you've got going, live or parked, across every repo
scruff fix-flaky-test # back later — rebuild the checkout, reopen the agent, where you left off
scruff reap # sweep every lane whose branch landed and nobody's standing inAnd never git stash again. The stash stack lives in the shared .git
dir, so every worktree of a repo and the main checkout push and pop the same
one — parallel agents routinely pop each other's work into a tree that never
asked for it. scruff park commits your dirty tree as a single wip: commit on
the branch only this pane has checked out; scruff unpark puts it back.
nix run github:hausfold/scruff # try it
nix profile install github:hausfold/scruff # keep it
go install github.com/hausfold/scruff/cmd/scruff@latest # or, with Go 1.26+Needs git. gh and lsof are optional and make it sharper; without them
scruff degrades toward keep, never toward delete. On
haus it's already on PATH, wired to ⌘↵.
Not "makes worktrees" — every vendor does that. scruff's product is the state machine and three invariants, in this priority order:
- Never lose work. Every destructive path parks first. The failure direction is always a branch lingers, never a tree vanished.
- Never reap what's in use. Occupied, dirty, or not provably landed ⇒ keep. Uncertainty resolves to keep — including when GitHub is unreachable.
- The registry is truth. Not the filesystem, not
git worktree list— those are derived, and they lie.
So a command exiting 2 — refused for safety is scruff working, not scruff
failing. Don't reach for git worktree remove; ask it why.
📖 hausfold.co/docs/scruff — and it only
lives there: installing,
working in lanes,
parking,
landing and cleanup,
runtimes,
config,
hooks and seams,
every verb and exit code,
the --json payload, and
the five SDKs.
Inside a haus machine it's the AI
room, which puts scruff on your PATH
and binds ⌘↵ to it.
Five clients over the same CLI, sharing one version number:
sdk/ts · sdk/python · sdk/rust ·
sdk/go · sdk/swift (published from a generated
mirror, hausfold/scruff-swift —
send changes here, never there). Install lines, the two shapes of usage and
leases: hausfold.co/docs/scruff/sdks.
ai/SKILL.md— the agent surface: drop it in and your agent drives scruff correctly first tryai/handoff/SKILL.md— the companion skill: write a brief a cold session can act on, andscruff spawn --prompt-fileit into its own laneAGENTS.md— hacking on scruff: the invariants, the frozen contracts, whatmake checkdoesn't coverSPEC.md— the design of record, and the contracts that are frozendocs/releasing.md— how the CLI and all five SDKs are cut from one numberscruff --help— the exhaustive flag list
Nothing here takes something it can't give back — a dirty tree is parked as a commit, and only merged, unoccupied lanes are ever reaped. That's the intent, not a warranty: keep a backup, and tell us what breaks.
MIT · one of the hausfold repos — haus rebuilds the Mac, this is what its agent panes stand on