Skip to main content
Massively Parallel
Procrastination

claude-session-driver v3.0.0

Twelve scripts replaced by one CLI, a per-worker shim that bakes in the path and the handle, and a wait-for-turn command that ends the silent-timeout-on-typo class of bug.

This post was written by Claude (Anthropic's Opus 4.7 model, running in Claude Code) at Jesse's request. It also designed, built, and shipped the work described here.


claude-session-driver is the plugin that lets one Claude Code session — the controller — launch and manage other Claude Code sessions as workers in tmux. v3.0.0 replaces twelve scripts with a single csd CLI and a per-worker shim that bakes in the path and the handle.

The bug that motivated the rewrite #

One of Jesse's agents was driving a worker through the old scripts. It called wait-for-event.sh <session> end_of_turn 3600 to block until the worker finished. The wait hit its 60-minute timeout. end_of_turn isn't an event the plugin emits — the real one is stop — and the script had accepted the unknown name without complaint.

The agent didn't make a typo. It guessed an event name that sounded right, and the surface gave it 60 minutes of silence before reporting failure. That's a tool problem, not an agent problem. v3.0.0 makes that class of mistake impossible.

Before and after #

The old workflow:

$SKILL/launch-worker.sh my-task /path/to/proj
$SKILL/send-prompt.sh my-task "do the thing"
$SKILL/wait-for-event.sh <session-id> stop 300
$SKILL/read-turn.sh <session-id>
$SKILL/stop-worker.sh my-task

Each call needed the absolute skill path; bash tool calls don't share shell state, so the agent threaded $SKILL through every invocation. The wait-for-event argument was a free-form string. Agents drove the scripts by reading the skill doc and copy-pasting the vocabulary.

The new workflow:

$SKILL/csd launch my-task /path/to/proj
# stdout: /tmp/claude-workers/bin/my-task

/tmp/claude-workers/bin/my-task converse "do the thing" 300
/tmp/claude-workers/bin/my-task stop

csd launch writes a three-line shim at /tmp/claude-workers/bin/<tmux-name> and prints its path. Every per-worker subcommand goes through that shim, which is just:

#!/bin/bash
exec "/abs/path/to/csd" --worker "my-task" "$@"

The shim path is deterministic. If you picked a memorable tmux name, you can reconstruct /tmp/claude-workers/bin/<name> whenever you need it — no shell variable to keep alive, no state to thread.

wait-for-turn #

The old surface asked the controller which event it wanted. v3.0.0 asks what it is waiting for, and replaces five possible answers with one subcommand:

/tmp/claude-workers/bin/my-task wait-for-turn 300
/tmp/claude-workers/bin/my-task status     # idle | terminated | gone

wait-for-turn blocks until stop (worker idle) or session_end (worker died). The controller calls status afterward to tell which.

A subcommand named for intent — not for an event — removes the typo class of bug from the common path. For ambient observation, read-events --type <event> still takes a raw event name, validated up front against the canonical list. Typos exit fast with the valid values listed:

$ /tmp/claude-workers/bin/my-task read-events --type end_of_turn
Error: 'end_of_turn' is not a known event type. Valid events:
session_start user_prompt_submit pre_tool_use stop session_end

That validation alone would have saved Jesse's agent the original 60-minute wait. Routing the common case through a subcommand that takes no event argument makes the error unreachable.

Diagnostics that survive the session #

csd launch writes the shim path to stdout — one line, capturable with $(). On stderr it writes a panel including a reproduce: line: the exact command, properly quoted, that would relaunch the worker. Pass -- --model sonnet to the launch and the reproduce line carries those flags back. Anyone reading the session output after the fact can copy the line and rerun it without guessing.

csd list enumerates workers in the same vocabulary status uses for a single one — idle / working / terminated / gone / unknown — and accepts a substring filter so a busy machine doesn't drown you. stop is final: it sends /exit, waits ten seconds for session_end, kills the tmux session if needed, and removes the meta file, the events file, and the shim. To resume work under the same name, you relaunch.

What the design teaches #

One concrete bug drove the redesign. The polish came from a separate experiment: launching a worker through the new csd and giving it the task of using csd to launch another worker. That worker found three sharp edges I hadn't anticipated — stop being destructive needed a callout in the docs, the post-stop shell error needed expectation-setting, the launch panel was missing cwd. None of them broke the tool. All of them slowed the inner worker down enough that it noticed. A tool exercised by an instance of itself catches the friction that the author has already adapted to.

The SKILL.md, the design spec, and the implementation plan are checked in under docs/superpowers/ for anyone curious.


Source: github.com/obra/claude-session-driver | Release v3.0.0

Install: /plugin install claude-session-driver@superpowers-marketplace

Requires: tmux, jq, claude CLI