skills/driving-claude-code-sessions/SKILL.md
Use when acting as a project manager that delegates tasks to other Claude Code sessions - launch workers, assign them work, monitor progress, review their tool calls, and collect results
npx skillsauth add obra/claude-session-driver driving-claude-code-sessionsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
4 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You can launch other Claude Code sessions as "workers" in tmux, send them prompts, wait for them to finish, read their output, and hand them off to a human. Workers run with --dangerously-skip-permissions, so they execute tool calls without prompting. A plugin (claude-session-driver) emits lifecycle events to a JSONL file so the controller can observe what the worker is doing.
All operations go through a single CLI: csd. After launching a worker, the controller receives a shim path at /tmp/claude-workers/bin/<tmux-name> that bakes in the worker handle. Every per-worker operation goes through that path — no positional state to thread between calls, no absolute skill path to prepend. A small set of environment variables tune behavior; see Environment variables at the bottom.
The shim path is deterministic: if you pick a memorable tmux name at launch, you can reconstruct /tmp/claude-workers/bin/<tmux-name> whenever you need it. For agents driving via tool calls, that's the right model — shell state doesn't persist between calls, so a SHIM=...; $SHIM cmd pattern just adds noise. The examples below use the bare path.
The CLI lives at <skill>/scripts/csd. Three top-level subcommands need the skill path:
csd launch <tmux-name> <cwd> [-- claude-args...] — bootstrap a workercsd adopt <tmux-name> <cwd> <session-id> [-- claude-args...] — re-adopt an existing Claude session as a worker (see Recovering workers)csd list [--all] — enumerate workerscsd grant-consent — one-time consent for --dangerously-skip-permissionsOnce a worker is launched, run subsequent commands against /tmp/claude-workers/bin/<tmux-name>:
SKILL=/abs/path/to/skill/scripts
$SKILL/csd grant-consent # one-time per machine
$SKILL/csd launch my-task /path/to/project # stdout: /tmp/claude-workers/bin/my-task
/tmp/claude-workers/bin/my-task status # use the shim directly
Pick a memorable tmux name at launch; the shim path is then deterministic. (You can capture it into a shell variable in an interactive shell, but for agent-driven workflows the bare path is simpler — there's no shell state to lose between calls.)
In examples below, $SKILL is the absolute path to skills/driving-claude-code-sessions/scripts. WORKER is the bare shim path (e.g. /tmp/claude-workers/bin/my-task) — substitute the deterministic path for your worker.
$SKILL/csd launch my-task /path/to/project
# stdout: /tmp/claude-workers/bin/my-task
# stderr: Worker launched. tmux/session_id/cwd/events/reproduce
csd launch:
/tmp/claude-workers/bin/my-tasksession_startreproduce: line is the exact command to relaunch with the same argsPass claude CLI args after a -- separator:
$SKILL/csd launch my-task /path/to/project -- --model sonnet
/tmp/claude-workers/bin/my-task converse "Refactor the auth module" 300
converse sends the prompt, waits for the worker to finish, and prints the final assistant text on stdout. For tool-heavy turns where the bare text strips the interesting part, use --with-turn to get the full markdown:
/tmp/claude-workers/bin/my-task converse --with-turn "Run the failing tests" 600
Multi-turn just works — the wait tracks turn boundaries automatically:
/tmp/claude-workers/bin/my-task converse "Write tests for the auth module" 300
/tmp/claude-workers/bin/my-task converse "Add edge cases for expired tokens" 300
If you need to drive the worker more directly:
/tmp/claude-workers/bin/my-task send "Refactor the auth module" # send without waiting
/tmp/claude-workers/bin/my-task wait-for-turn 300 # block until stop or session_end
/tmp/claude-workers/bin/my-task status # idle | working | terminated | gone | unknown
/tmp/claude-workers/bin/my-task read-turn # last turn as markdown (tool results truncated to 5 lines)
/tmp/claude-workers/bin/my-task read-turn --full # last turn with complete tool results
Every tool call emits a pre_tool_use event with the tool name and input. Tail the event stream to watch in real time:
/tmp/claude-workers/bin/my-task read-events --follow &
MONITOR_PID=$!
# ... do other work ...
kill $MONITOR_PID
Or pull events after the fact:
/tmp/claude-workers/bin/my-task read-events # all events
/tmp/claude-workers/bin/my-task read-events --last 5
/tmp/claude-workers/bin/my-task read-events --type pre_tool_use
--type accepts one of: session_start, user_prompt_submit, pre_tool_use, stop, session_end. Unknown event names fail fast.
If you see something you don't want, stop the worker:
/tmp/claude-workers/bin/my-task stop
/tmp/claude-workers/bin/my-task stop
Sends /exit, waits up to 10s for session_end, kills the tmux session if still running, and removes the meta, events, and shim files.
stop is destructive: the worker is gone and the shim path stops working. If you wanted the worker around for follow-up turns or a parallel workflow, don't call stop until you're done with it. To resume work under the same name, relaunch — csd launch my-task /path/to/project again — and you'll get a fresh worker at the same shim path.
After stop, the shim no longer exists, so invoking it again surfaces a shell error along the lines of no such file or directory: /tmp/claude-workers/bin/my-task (the exact wording depends on your shell). That's expected; the worker is gone.
/tmp/claude-workers/bin/my-task handoff
Prints attach instructions for a human to take over the tmux session.
$SKILL/csd list # live workers (idle/working/terminated)
$SKILL/csd list --all # include 'gone' workers (tmux already exited)
$SKILL/csd list api # substring filter on tmux name
csd launch <tmux-name> <cwd> [-- claude-args...]
csd adopt <tmux-name> <cwd> <session-id> [-- claude-args...]
csd list [--all] [<pattern>]
csd grant-consent
<shim> converse [--with-turn] <prompt> [timeout=120]
<shim> send <prompt>
<shim> wait-for-turn [timeout=60]
<shim> status
<shim> read-events [--last N] [--type T] [--follow]
<shim> read-turn [--full]
<shim> stop
<shim> handoff
<shim> session-id
<shim> events-file
<shim> is /tmp/claude-workers/bin/<tmux-name>. Run csd help for the same surface.
$SKILL/csd launch worker-api ~/proj
$SKILL/csd launch worker-ui ~/proj
/tmp/claude-workers/bin/worker-api send "Add pagination to /users"
/tmp/claude-workers/bin/worker-ui send "Add a loading spinner to the user list"
/tmp/claude-workers/bin/worker-api wait-for-turn 600
/tmp/claude-workers/bin/worker-ui wait-for-turn 600
/tmp/claude-workers/bin/worker-api stop
/tmp/claude-workers/bin/worker-ui stop
$SKILL/csd launch spec ~/proj
/tmp/claude-workers/bin/spec converse "Write an OpenAPI spec for /users to /tmp/api.yaml" 300
/tmp/claude-workers/bin/spec stop
$SKILL/csd launch impl ~/proj
/tmp/claude-workers/bin/impl converse "Implement the endpoint defined in /tmp/api.yaml" 600
/tmp/claude-workers/bin/impl stop
wait-for-turn matches stop OR session_end, so it returns when the worker dies. Call status afterward: if it's gone, the worker crashed.
Worker runtime state (the meta/events/shim files under /tmp/claude-workers) lives in /tmp, which macOS clears on reboot — and the tmux panes die with it. But the conversations survive: Claude Code persists each session transcript at ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl. csd adopt brings one back as a live, driveable worker:
$SKILL/csd adopt my-task /path/to/project <session-id>
# stdout: /tmp/claude-workers/bin/my-task (same shim contract as launch)
adopt pre-writes the meta keyed by <session-id>, starts claude --resume <session-id> (which preserves the id, so the worker emits events normally), and writes the shim — so the resumed conversation is fully driveable (converse/status/read-turn/…), with all prior context intact. If a tmux session of that name already exists (e.g. restored by tmux-resurrect / tmux-continuum), adopt respawns its pane in place, preserving the restored layout; otherwise it opens a new one.
Find a worker's <session-id> from its working directory: the newest *.jsonl in ~/.claude/projects/<cwd with every / . _ replaced by ->. For bulk recovery (e.g. pairing with tmux-continuum's @continuum-boot), examples/recover-workers.sh reads a tmux-resurrect snapshot, derives each id, and calls adopt per worker — run it with --dry-run first. Note: workers are restored as resumed sessions, not their original tool/MCP state; re-pass any launch args (e.g. -- --model …) you depended on.
If you know the tmux name, the path is /tmp/claude-workers/bin/<tmux-name>. If you don't, csd list enumerates everything; csd list <pattern> filters by tmux-name substring.
send uses bracketed-paste, which handles multi-line and special characters. For prompts in the tens-of-KB range, write to a file and tell the worker to read it:
echo "Long instructions..." > /tmp/instructions.txt
/tmp/claude-workers/bin/my-task send "Read /tmp/instructions.txt and follow it"
The csd CLI honors a small set of env vars. All are optional.
| Variable | Purpose |
|---|---|
| CSD_CLAUDE_BIN | Path to the claude binary. Defaults to claude (resolved via PATH). Set when claude is not on PATH or you want to pin a specific version. |
| CSD_CONVERSE_DIAG_FILE | When set, csd converse writes a post-mortem diagnostic on timeout — ps tree, tmux capture-pane, last 30 lines of the claude session JSONL, last 20 lines of the csd events JSONL — to this path, then emits a csd-diagnostic: <path> pointer to stderr. The file is overwritten on each timeout. Unset = no diagnostic file. Useful when wrapping csd in a harness that can ship the file off-box before the worker is reaped. |
| HOME | Used to locate ~/.claude/projects/<encoded-cwd>/<sid>.jsonl and the one-time consent file (~/.claude/.claude-session-driver-consent). |
The same list is shown by csd help.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.