# What scruff is (/docs/scruff)



Run two coding agents in one checkout and they fight. One switches the branch
out from under the other, uncommitted edits collide, and closing a pane
mid-thought can take work with it.

The fix is a worktree each, and making one is table stakes: your client probably
has a flag for it already. What nothing owns is the rest of the life. The branch
still alive after the pane died. The checkout nobody is sitting in. The tree with
forty uncommitted minutes in it. The branch whose PR merged yesterday and has
kept committing since.

**scruff** owns that. One Go binary, any git repo, any client, and no more
reading `git worktree list` trying to remember which rows are safe to delete.

```sh
cd "$(scruff new fix-flaky-test)"  # a lane on this repo: new checkout, new branch, path on stdout
scruff                             # every lane you have 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 is standing in
```

It is [MIT licensed](https://github.com/hausfold/scruff/blob/main/LICENSE), it
installs standalone with no Nix, and it has nothing to sign in to.

## A lane [#a-lane]

The unit is a **lane**: one agent's branch, checkout and pane, from create to
reaped.

Not a *worktree*, git's word for the checkout on disk, because a parked lane has
no checkout at all and the branch is what survives. Not an *agent*, which here
means the client a lane runs (`claude`, `codex`, `opencode`, `pi`). Not a
*session*, which is your client's word for a conversation and your multiplexer's
for a pane. Those three words are reserved.

```
create ──▶ live ◀──▶ parked
             └────┬────┘
                  ▼
               landed ──▶ reaped
```

A lane is **live** while its checkout exists and **parked** once the pane closed
and the checkout went; the two trade places every time you close a window and
come back. **Landed** is not something you do to a lane. It is the verdict that
its work reached the default branch, and the only thing that lets the sweep
take it. `scruff drop`, a person typing one lane's name, is the one exception.

The **branch is the durable artifact and the directory is disposable**, which is
most of scruff's behaviour in one line: closing a pane can delete a checkout
without anybody losing anything, and `scruff <name>` builds it back.

## Three invariants, in this order [#three-invariants-in-this-order]

scruff's product is not "makes worktrees". It is this state machine and three
promises, in priority order:

1. **Never lose work.** Every destructive path parks first. The failure
   direction is always *a branch lingers*, never *a tree vanished*.
2. **Never reap what is in use.** Occupied, dirty, or not provably landed means
   keep. Uncertainty resolves to keep, including when GitHub is unreachable.
3. **The registry is truth.** Not the filesystem, not `git worktree list`. Those
   are derived, and they lie: stray directories, half-removed checkouts, parked
   branches with no directory at all.

<Callout type="info" title="Exit 2 is scruff working, not scruff failing">
  A command that refuses with **exit 2** has decided that keeping was safer than
  removing. It is not an error to route around: don't reach for `git worktree
  remove`, ask it why. [Every exit code](/docs/scruff/cli#exit-codes) means a
  different recovery.
</Callout>

## What it will not do [#what-it-will-not-do]

The thesis is **substrate, not orchestrator**, and the non-goals are
load-bearing: no scheduling, no agent supervision or restart, no fullscreen TUI,
no hosted anything, no knowledge of your build system or package manager or CI,
no merge conflict *resolution*, and no opinion about which agent you run. The
actions at each transition belong to you.

It is also repo-agnostic and client-agnostic all the way down. It knows nothing
about the family that wrote it, and where one of its answers is a house rule
wearing a universal name ("landed means merged into the default branch",
"resume means become the client process"), that answer is a
[seam](/docs/scruff/seams) you can replace with a program of your own.

## Made for agents [#made-for-agents]

scruff is a CLI with a frozen JSON contract, which is exactly the shape an agent
drives well: `scruff --json` for the whole picture, `scruff watch --json` for a
live NDJSON feed, six exit codes that each mean a different recovery, and
[five SDKs](/docs/scruff/sdks) over one wire format.

It also ships **two agent skills** in the repo, so a session that has never seen
scruff still drives it correctly the first time: `scruff` teaches the lifecycle,
and `handoff` teaches the one thing with no verb, writing a brief a cold session
can act on. [For coding agents](/docs/scruff/agents) is what is in them and how
to install them.

## Questions [#questions]

**Does it need GitHub?** No. `git` is the only requirement. With
[`gh`](https://cli.github.com) signed in, scruff can also prove a branch landed
through a squash merge, which git alone cannot see; without it, or on another
forge, a squash-merged lane reads *not landed* and waits for you rather than
being taken. Keeping more is the only way it degrades.

**Which agents?** It opens and resumes `claude`, `codex`, `opencode` and `pi`,
and `--cmd` runs anything else in a fresh lane. A lane is an ordinary directory
on an ordinary branch, so every editor and shell you already use works in one.

**I already use `claude --worktree`.** Keep it. Point Claude Code's
`WorktreeCreate` and `WorktreeRemove` hooks at `scruff hook create` and
`scruff hook remove`, and every one of those checkouts lands in the registry,
gets parked instead of deleted, and shows up in `scruff`. [The
wiring](/docs/scruff/seams#client-hooks-the-other-direction) is one block of
JSON.

**Does it touch my main checkout?** Lanes live outside the repo, under
`~/.cache/scruff`, so your working tree never gains a directory, and the sweep
only ever looks at lanes: your main checkout is not one, and the checkout scruff
is run from is never swept either.

## Inside a haus machine [#inside-a-haus-machine]

scruff runs perfectly well on its own, and that is how most people meet it. It is
also what the **AI room** in [haus](/docs/haus) stands on: the layer takes scruff
as a flake input, puts it on your `PATH&#x60;, and binds &#x2A;*⌘⏎** to open a lane on
whatever repo the focused window is looking at. Same binary, same lanes; only
the wiring differs. See [the AI room](/docs/haus/rooms/ai).
