Working in lanes
Open a lane on this repo or another one, hand it to an agent on a first-turn task, list what you have going, and pick any of it back up later.
Everything here is one of four moves: open a lane, list them, resume one, or go to the window one is already running in.
Open one, on this repo
cd "$(scruff new)" # a random two-word name
cd "$(scruff new fix-flaky-test)" # or your ownscruff new prints the new checkout path and nothing else on stdout, which
is what makes cd "$(…)" work. Every diagnostic goes to stderr. It is a
contract, not a style choice.
The checkout lands outside the repo, at
~/.cache/scruff/<repo>/<name>, on a worktree-<name> branch cut from the
repo's local HEAD. Your own working tree never gains a directory, so no build
tool trips over it.
To hand the pane straight to an agent instead of to your shell:
scruff new fix --open # your default client
scruff new fix --open codex # or name one
scruff new fix --agent opencode # same choice, as a flag
scruff new fix --cmd 'nvim .' # or something that isn't an agent at allOpening it on a task, not a blank pane
scruff new --prompt 'the bar paints draft PRs in the merged green'
scruff new --prompt-file brief.md # or `-` for stdin
scruff new --prompt '…' --image screenshot.pngA prompt implies --open: the client starts on a first turn rather than an
empty prompt. --prompt-file is the one to reach for when the task is a
brief, which is usually a markdown list several paragraphs long. scruff puts a
-- before the prompt when the client supports one, because a task that starts
with a dash is otherwise read as a flag and the pane dies before the agent
draws.
A lane can name itself after the task
Give a lane a prompt and no name, set namer = "claude" in the config, and it
comes out hud-draft-color instead of cozy-otter. One line of config, no API
key, and it can never cost you a lane. See
naming a lane after its task.
Open one on another repo
An agent working in one repo often has to change another one. Never a raw
git worktree add: that skips the registry, so nothing downstream ever learns
the checkout exists.
cd "$(scruff child ~/code/other-repo)"
cd "$(scruff child ~/code/other-repo review-api)" # named, if you likechild records the new lane as a child of the pane you ran it from, which
is what lets a status bar show its PR beside the work that spawned it. It prints
only the path, same as new.
It is that pane's second checkout rather than a second agent: no window and no
conversation of its own. scruff lists it under the lane that made it, and
--json says so in chat — which is what a picker should
read before deciding a lane has nowhere to go to.
spawn is child for a caller with no pane of its own: a launcher, a cron job,
an orchestrator.
scruff spawn ~/code/other-repo task-42 --prompt-file brief.mdThe lane is opened through the open seam, so exit 3
means the lane exists and this machine simply has no hook to put a window on it.
Report the command scruff printed rather than retrying. With a --prompt and a
namer configured, the name argument is optional.
List what you have going
scruff🌫 lanes you can resume (scruff <name>, or <repo>/<name>)
repo name state agent last commit
pounce smart-ranking live claude launcher: fixes from the p…
nebelung sparkle parked claude wip: auto-saved on lane close
haus omit-child-lanes live+3 claude bar: the draft PR colour
perch └ omit-child-lanes live claude cli: the shelf takes a pathEvery repo, in one list. The state column is live, parked or stray, and it
can carry a suffix: live+3 is three commits past a merged
PR, ~3 is the lookalike that
wants the opposite treatment.
A lane sits under the one that spawned it, marked └. It is nested rather
than hidden: its branch and its PR are its own, closing the parent's pane does
not reap it, and this listing is where it surfaces once that pane is gone.
scruff --json is the same listing for machines, and its shape is
a frozen contract.
A listing is not read-only
Every scruff and scruff --json also sweeps parked branches that have since
landed. It is harmless, the invariants still hold, but don't poll it under the
impression that you are only looking.
Pick one back up
The work is on the branch and the conversation is in your client's own transcript, so a closed window is never a dead end.
scruff sparkle # rebuild the checkout, reopen the agent
scruff nebelung/sparkle # qualify when the name exists in two repos
scruff sparkle --pick # choose from the client's session list insteadA parked lane has no checkout on disk; resuming builds it back from the branch. scruff reopens whichever client the lane was made with, so a Codex lane comes back in Codex however your default has moved since.
By default it continues the newest conversation in that checkout, because a
lane's own directory is one only that lane's agent ever ran in, and offering a
picker there asks you to answer a question with one answer. --pick is for when
the newest is not the one you wanted. The exception scruff makes for itself: a
lane spawned by scruff child, whose conversation lives in a shared parent
checkout full of unrelated sessions, gets the picker, because there scruff
genuinely cannot name the right chat.
Nothing records a session id, deliberately. A recorded id goes stale the moment a session is forked or compacted, and it fails silently into the wrong chat, which is worse than a picker.
Go to the one that is already open
scruff focus sparklefocus is resume's narrower sibling: the lane is already running and the only
question is which window to raise. scruff cannot answer that on its own, because
the join from a lane to a window belongs to whatever opened it, so this is a
seam a desktop fills in. Without one it falls back to
resume, which opens a window: a detached lane is running, not gone.
Its usual caller is not a human. On a machine with trill, clicking a lane's notification runs exactly this.
The two traps on day one
Commit something immediately
A brand-new lane has zero commits of its own, so it is trivially contained
in the default branch and another session's scruff reap may take it. scruff
reports that state as fresh rather than merged, but the checkout is still
sweepable. Make a commit and it stops being.
A missing directory is not lost work
Closing a pane parks the tree and deletes the checkout. That is the design, not
a failure: the branch is the durable half. scruff <name> puts the directory
back.
Install
One binary, no runtime, no account. Nix or Go, the two optional tools that make it sharper, and the one line that proves it took.
Parking work
Set a dirty tree aside as one wip commit on the branch only this pane has checked out. The reason git stash is the wrong tool the moment you have two agents.