# Working in lanes (/docs/scruff/lanes)



Everything here is one of five moves: **open** a lane, **list** them, **resume**
one, **go to** the window one is already running in, or **notice** what the
other lanes have already changed before you collide with them.

## Open one, on this repo [#open-one-on-this-repo]

```sh
cd "$(scruff new)"                 # a random two-word name
cd "$(scruff new fix-flaky-test)"  # or your own
```

`scruff 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:

```sh
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 all
```

### Opening it on a task, not a blank pane [#opening-it-on-a-task-not-a-blank-pane]

```sh
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.png
```

A prompt implies `--open`: the client starts on a first turn rather than an empty
prompt, and `--prompt-file` is the one to reach for when the task is a *brief*
several paragraphs long. scruff puts a `--` before the prompt where the client
supports one, because a task starting with a dash is otherwise read as a flag and
the pane dies before the agent draws.

<Callout type="info" title="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](/docs/scruff/config#naming-a-lane-after-its-task).
</Callout>

## Open one on another repo [#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.

```sh
cd "$(scruff child ~/code/other-repo)"
cd "$(scruff child ~/code/other-repo review-api)"   # named, if you like
```

`child` 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`](/docs/scruff/json), 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.

```sh
scruff spawn ~/code/other-repo task-42 --prompt-file brief.md
```

The lane is opened through the [`open` seam](/docs/scruff/seams), 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.

Pass the name as `--derived-name <name>` rather than positionally when your
caller worked it out from the task. A positional name is one you typed, and scruff
[refuses one over the machine's ceiling](/docs/scruff/config#how-long-a-lane-name-may-be)
rather than shortening it; a derived name is cut to fit instead, which is what a
launcher with nowhere to print a refusal needs.

## List what you have going [#list-what-you-have-going]

```sh
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 path
```

Every 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](/docs/scruff/landing#when-a-lane-outran-its-pr), `~3` is the lookalike that
wants the opposite treatment.

A lane in **another repo** sits under the one that spawned it, marked `└`, and it
is nested rather than hidden: its branch and its PR are its own, and closing the
parent's pane does not reap it. A lane in the *same* repo is a sibling however it
was opened, with its own window and its own branch off the same main, so it draws
at the top level.

`scruff --json` is the same listing for machines, and its shape is
[a frozen contract](/docs/scruff/json).

<Callout type="warn" title="A listing is not read-only">
  Every `scruff` also sweeps parked branches that have since landed. Harmless, and
  the invariants still hold, but it is not a read-only command.
</Callout>

## Pick one back up [#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.

```sh
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 instead
```

A 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**: a lane's
own directory is one only that lane's agent ever ran in, so 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 one exception is a lane spawned by `scruff child`, whose
conversation lives in a shared parent checkout full of unrelated sessions, so it
gets the picker.

Your client keys its history on the exact directory. A lane whose checkout has
moved would open an empty session, so scruff brings the conversation over from
the old path and says where it came from. It copies, so the original stays put.
If two old paths could be the lane's it takes neither, and `scruff doctor` names
them. A lane with no conversation anywhere opens a fresh one instead of a session
that exits.

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**.

## Go to the one that is already open [#go-to-the-one-that-is-already-open]

```sh
scruff focus sparkle
```

`focus` 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](/docs/scruff/seams) 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](/docs/trill), clicking a lane's notification runs exactly this.

## Notice the other lanes [#notice-the-other-lanes]

Lanes collide by accident: two of them append to the same `AGENTS.md`, or
rewrite the same paragraph, and nobody finds out until a PR won't merge.
Because every lane of a repo is a branch in **one shared object store on one
machine**, nothing has to be declared to see it coming. Which files, which
regions, whose work is where: all of it is measured, offline, in milliseconds.

```sh
scruff overlap                 # who is in your files, and where
scruff overlap --brief         # the roll call, prose dropped: run it when a lane starts
scruff overlap --path doc.md   # that file only; prints NOTHING when it is clear
scruff overlap                 # from the MAIN checkout: every pair of lanes sharing a file
scruff overlap --pair a b      # two lanes by name, from anywhere
```

```
≋  overlap — 3 other lane(s) on haus; 1 in your way, 1 nearby
   ⚠ tidy-raccoon   AGENTS.md L212-240
   · wobbly-weasel  README.md elsewhere in the file
   tidy-raccoon — “bar: the draft PR colour, and the test that pins it”
      ↳ tidy-raccoon lands first (already pushed) — then cuddly-sparrow rebases onto main
   merge-tree: clean against every lane and origin/main
```

|                 | Means                                                                                                                                                 | Do                                                                             |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `⚠`             | you and that lane changed the **same region** of the same file, within git's own three lines of context: what a merge presents as one conflicted hunk | move, narrow, or take the `↳` order into your PR body                          |
| `·`             | the same file, different regions                                                                                                                      | nothing. Co-editing a long shared file is normal                               |
| `✗`             | `git merge-tree` says the two committed tips **already** conflict                                                                                     | rebase onto the default branch once the other lands; never merge it into yours |
| the quoted line | that lane's last commit subject: its intent, for free                                                                                                 | read it before deciding whose work moves                                       |

Two signals, reported apart so they can disagree. The **hunk index** is every
lane's changed line ranges since your common ancestor, **uncommitted and
untracked work included**: the half `merge-tree&#x60; structurally cannot see, and
where a lane spends most of its life. &#x2A;*`merge-tree`** is a real three-way
merge of the two branches with no working tree touched: exact, committed work
only. A parked lane is read from its branch, a live one from its checkout.

A lane with **nothing left to land is left out entirely**. A branch outlives
its own squash merge (the merge is a brand-new commit, so the branch is never
an ancestor of main) and would otherwise claim every line it shipped for as
long as it exists. `overlap` reads content, not ancestry: merged into main in
the object store, a lane whose tree does not move main's has nothing to
collide with. One that shipped and then kept committing is still holding those
commits, and stays.

The `↳` line is a default, not a rule: it reads only facts both lanes can see
(who has pushed, whose diff is bigger, then the alphabet) so two agents reach
the **same** answer without talking. Exit **0** clear, **3** same file, **4**
same region or a `merge-tree` conflict. A finding, never a refusal. It cannot
see semantic collisions across files, a rename on one side against an edit on
the other until both have committed, or another repo at all: a `scruff child`
lane has its own object store and cannot textually collide with this one.

## The two traps on day one [#the-two-traps-on-day-one]

<Callout type="warn" title="Commit inside the first hour">
  A brand-new lane has **zero commits of its own**, so it is trivially contained in
  the default branch. scruff reports it as [`fresh` rather than
  merged](/docs/scruff/landing#the-four-verdicts) and keeps it out of the sweep for
  its first hour.

  That hour is there because of who is holding the lane. A shell or a client
  sitting in the checkout holds it, and a tool that runs each command from a fresh
  directory holds nothing at all, which is every agent between two tool calls. Make
  a commit inside the hour and the lane stops being sweepable on its own; leave it
  longer and a `scruff reap` run from anywhere else on the machine will take it.
</Callout>

<Callout type="info" title="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.
</Callout>
