Skip to content

Coding agents (wt)

If you drive Claude Code from a nebelhaus machine, the rice hands you a small tool called wt — it makes running several agents at once safe, and makes closing a pane something you never have to think about. It works on any git repo, not just nebelhaus, and you get it for free the moment the rice is on your PATH.

Run two Claude agents in the same checkout and they fight: one switches the branch out from under the other, uncommitted edits collide, and closing a pane mid-thought can lose work. wt fixes both by giving every agent its own checkout.

Press Super c (⌘C) in any repo’s terminal tab and the rice spawns a fresh Claude Code session in an isolated git worktree:

  • its own checkout on a worktree-<name> branch, branched from the repo’s local HEAD;
  • living outside the repo, under ~/.cache/claude-worktrees/<repo>/<name>, so your working tree stays clean and no build tool ever trips over it;
  • so any number of agents can hack in parallel without ever touching each other’s branch — or yours.

Super ⇧c (⌘⇧C) spawns exactly the same isolated agent, but in place of the focused pane instead of splitting a new one beside it — for when the tab is already as subdivided as you want it and the pane you’re looking at has done its job. The pane it replaces is only suspended, never killed: quit the agent and your shell comes back, focused, scrollback intact.

Ctrl Alt Shift c is the resident variant: the one agent per tab that’s allowed to edit the checkout you’re actually looking at.

Prefer the launcher? Spawn Agent in Pounce (⌘Space, type “spawn”) does the same thing from the GUI, for whichever client you’ve made the default: pick a repo, type what you want done, and it creates a worktree named after the task — not the random luminous-twirling-codd an unattended agent would coin — and drops a session into that repo’s tab. Naming the branch from the prompt is the whole point; the palette is just the shortest path to it.

Because the work lives on the branch (and the chat in your client’s transcript), a closed pane is never a dead end. Run wt bare to see everything you can pick back up, across every repo:

Terminal window
wt # list every parked / live agent worktree, all repos
🌫 agent worktrees you can resume (wt <name>, or <repo>/<name>)
repo name state agent last commit
nebelung sparkle parked claude 2 hours ago — wip: auto-saved on pane close
pounce quick-emoji live codex 10 min ago — add fuzzy weights

To pick one back up, name it — wt rebuilds the checkout and reopens the chat in the same directory, so that worktree’s client resumes with its full history:

Terminal window
wt sparkle # rebuild the checkout + reopen the chat (that worktree's client)
wt nebelung/sparkle # qualify with the repo when a name exists in two repos

Spawn from Pounce — and choose the default client

Section titled “Spawn from Pounce — and choose the default client”

For a named worktree without first finding the right terminal tab, summon Pounce and run Spawn Agent. Pick a repository, describe the task, and it creates a worktree-<task-name> checkout before opening the configured client in that repo’s zellij tab.

Set the client declaratively:

nebelhaus.agents.default = "codex"; # or "claude" (default), or "opencode"

wt records that client with the worktree. Changing the option therefore only changes new spawns: a parked Codex task still reopens through codex resume, and an OpenCode task through opencode --continue.

The option drives the ⌘C keybinds and the c shell alias too. Only Claude Code can make its own worktree (its native --worktree flag, which fires wt’s create hook); for Codex and OpenCode, ⌘C runs wt new instead — the same checkout, branch, registry entry and parent session, made from the outside.

A separate option says which clients are installed:

nebelhaus.agents.clients = [ "claude" "opencode" ]; # the default

agents.default must be one of them, and the rebuild says so by name if it isn’t — which is the point: naming a client you didn’t have used to fail at spawn time, inside the pane, after the worktree already existed. Pounce’s Spawn Agent still checks command -v before it creates anything, for the case the assertion can’t cover (a hand-managed install that moved).

There is also Spawn Agent with Screenshot. It uses macOS’s normal area selector (screencapture -i) before the repository/prompt flow, then keeps the PNG under ~/.cache/nebelhaus-agent-screenshots/ for the agent. Codex receives it as an initial image; Claude and OpenCode receive its file path in the first prompt so they can inspect it. Keeping it as a separate palette entry matters: a task whose text happens to contain “screenshot” can never steal Return in the free-text prompt field.

Sometimes an agent working in one repo needs to change another one — say a session in the workshop that has to touch a sub-repo. Don’t reach for a raw git worktree add: that skips wt’s registry, so the statusline never learns to track it and its PR goes invisible. Use wt child instead:

Terminal window
workshop_root="$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")"
cd "$(wt child "$workshop_root/nebelung")"

That creates a worktree of the other repo, registers it as a child of your current pane (so its PR shows up in the statusline where you’re working), and prints only the new checkout path — hence the cd "$(…)". Resolving the workshop root through Git keeps the command independent of where you cloned it.

Most cleanup is automatic: closing a pane reaps a merged branch, and every wt listing first self-heals by sweeping parked branches whose PR has since merged. But some things end a pane without the graceful hook — a reboot, a crash, or a wt child checkout (which the hook never owns, since the pane that made it isn’t the one it belongs to). For those, sweep on demand:

Terminal window
wt reap # reap every LANDED worktree now — parked ones, plus
# clean & merged live checkouts no pane is open in

wt reap is deliberately conservative: it leaves anything dirty, anything unmerged, and any checkout a pane is still open in — not only yours but any other session’s, because a just-merged branch looks exactly like a finished one while its agent is still sitting in it. Only landed work that nobody is standing in gets removed.

Setting work aside: wt park, never git stash

Section titled “Setting work aside: wt park, never git stash”

git stash looks like it belongs to the checkout you’re standing in. It doesn’t. The stash stack lives in the repo’s shared .git dir, so every agent worktree of a repo — and the main checkout — push and pop the same stack. Two panes stashing at once means either can pop the other’s entry into a tree that never asked for it, which is a confusing mess at best and lost edits at worst.

So park instead of stashing:

Terminal window
wt park "half-done refactor" # whole dirty tree → one wip: commit on THIS branch
wt unpark # rewind it — changes back, uncommitted

wt park commits everything — tracked edits, untracked files, deletions — as a single wip: commit on the branch only this pane has checked out. It’s the same thing the pane-close hook does automatically, just on demand. Nothing is shared, so no other agent can touch it; it survives a pane close; and wt lists it as that worktree’s last commit. wt unpark puts the changes back in the working tree, uncommitted, exactly as they were.

Run agents in eight tabs and the question is always the same one: which of them is sitting on a permission prompt? The rice answers it in the tab bar itself — a filled badge beside the name of any tab holding agent panes. The badge carries a count — how many agents live in that tab — on a background coloured by what the most urgent one is doing:

BadgeMeans
peachan agent is blocked on you — a permission prompt, or it’s asking a question
skyan agent is mid-turn, working
greenthe agent finished its turn; nothing is waiting
(no badge)no agent panes in that tab

The number is the exact count of agent panes in the tab; the colour is the most urgent state among them, so peach always wins: the tab that needs you never hides behind a tab that’s merely busy. And it’s status, not a notification — a green badge stays green until that agent starts another turn or its pane goes away. Looking at it doesn’t dismiss it.

This is the same signal as the agents bar pill, reported by the client’s own lifecycle hooks (which is why it’s exact rather than guessed — the agent says what it’s doing, nothing scrapes the screen for it). The pill is for when you’re out in the GUI; the badge is for when you’re already in the terminal. It lights up as soon as an agent pane reports, and stays invisible if you never run one.

Every client reports through one command, agent-state <working|waiting|idle| remove> <client>, which the rice puts on your PATH:

ClientWiringWho writes it
OpenCode~/.config/opencode/plugin/nebelhaus-agent-state.jsthe rice, for you
CodexUserPromptSubmit / PermissionRequest / Stop in ~/.codex/hooks.jsonthe rice, for you — Codex then asks you to review and trust those hooks the first time it starts, which the rice deliberately doesn’t bypass
Claude CodeUserPromptSubmit / Notification / Stop / SessionEnd hooks in ~/.claude/settings.jsonyou — Claude owns that file and rewrites it, so the rice never touches your hooks

A pane’s row also disappears on its own once the pane is gone, whether or not its client said goodbye. That matters most for Codex, whose hooks have no session-end event at all — but it’s the same thing that clears the row of a Claude agent you killed, or one that crashed.

The rice points Claude Code’s status bar at a small HUD driven off wt’s registry, so you can see where every agent stands without leaving the pane. Row one is this session’s own worktree — its name, a status token, and its GitHub PR pill. The rows below are the worktrees this session spawned (across whatever repos they live in), each with its repo, name, token, and PR state. The cross-repo GitHub lookups run detached and cached, so the bar never stalls your prompt.

The token is one mutually-exclusive slot:

TokenMeans
branch is merged into main — closing the pane reaps the worktree
N^N commits on the branch, not merged yet
+A -Duncommitted line changes, when nothing’s committed yet
(nothing)identical to main — no news is good news
Section titled “Links — every URL a pane’s session saw”

The same statusline pass that drives the HUD also notes which Claude transcript each pane is running, and Pounce turns that into a command. Press ⌘L in the terminal (or summon the palette with ⌘Space and run Links) to get every URL from the focused pane’s whole Claude Code session — newest first, including links buried in collapsed tool output or that scrolled away hours ago. opens the pick in your browser.

GitHub PR and issue links come back enriched with their real title and open/closed/merged state — a parallel, cached gh fetch capped at ~2s, so a slow network degrades to plain links rather than a hung palette. A pane not running Claude Code falls back to its visible scrollback.

CommandWhat it does
wtList every parked/live agent worktree across all repos (self-heals first)
wt <name>Resume one: rebuild its checkout and reopen its recorded agent chat
wt resume <name>The same thing, spelled out
wt reapSweep every landed worktree now (keeps dirty/unmerged/any open pane)
wt child <repo>Make a worktree of another repo as a child of this pane (cross-repo)
wt park [label]Set the dirty tree aside as a wip: commit on this branch (use instead of git stash)
wt unparkUndo the last wip: commit — changes back in the working tree, uncommitted
wt new [name]Make a worktree of this repo and open the default client in it — what Super c runs when that client isn’t Claude Code
wt create / wt removeThe hooks Claude Code’s --worktree fires — you don’t call these by hand
wt agent defaultPrint the configured spawn client

Hacking on nebelhaus itself? The Contributing page shows how agent worktrees plug into the family’s bench try / PR / ship flow.