"Spawn a new Claude Code CLI session in a new terminal window with a given prompt. Use when another skill (such as escalation-watcher) needs to hand a task off to a fresh interactive Claude session running in its own terminal — for example, spawning `/unblock <task>` so a human can drive it. Triggers on phrases like 'spawn a claude session', 'open a new terminal with claude', 'launch /unblock in a new window'. NOT for: running arbitrary shell commands in a new terminal, or invoking Claude inside the current session (use a sub-agent for that)."
Recursos
2Instal·la
npx skillscat add leodearden/dark-factory/spawn Instal·la des del registre de SkillsCat.
Spawn
A one-shot helper that opens a new terminal window and launches an independent claude CLI session inside it with a given prompt. The spawn happens via a background Bash task that blocks until the spawned session exits, so the caller can treat the background task's completion as a reliable "session finished" signal — even on emulators (gnome-terminal, konsole, macOS Terminal) whose launcher process normally detaches immediately. The spawned session is otherwise fully independent — separate terminal, separate Claude conversation, separate tool state.
Use this whenever a skill needs to hand a task off to a fresh interactive Claude — most commonly, escalation-watcher spawning /unblock <task_id> so a human can drive resolution in a dedicated window.
Resolving the prompt for a fresh context
The spawned session begins with an empty conversation. Whatever prompt you pass becomes its first and only context. So the prompt must stand on its own there — anything that only means something here (in this conversation) is meaningless once it lands in the fresh session.
Before you build the script invocation, run this decision procedure on the prompt. Default to the least transformation — resolve only when there is a genuine unresolved reference:
Already self-contained → pass verbatim. A slash command whose argument fully specifies the work:
/unblock 8888,/review, an absolute path, a quoted literal. Change nothing. This is the common case, and the only case programmatic callers (e.g. escalation-watcher) ever hit. Never "enrich" a self-contained command — doing so risks corrupting a prompt that was already correct.Contains a contextual reference → resolve, then rewrite, keeping the command. Deixis or anaphora (
that,this,it,above,the fix,the approach we discussed) or a bare instruction that only parses given this conversation. Replace each reference with the concrete thing it denotes — drawn from this conversation, rendered unambiguously — and keep any leading slash command so the target skill still runs in the fresh session./do that→ resolvethat;/prd that feature→ resolvethat feature.
For two commands the resolution is richer than swapping a pronoun, because the fresh session is autonomous and cannot ask you the questions it would normally ask:
/do — distill here, execute autonomously there
/do's real work is compressing this session into a self-contained plan. The spawned session can't do that (the session is gone), so do it here, then hand the spawned session pure execution:
- Produce the plan exactly as the
/doskill body prescribes: Objective, Decisions & rationale (including the alternatives you rejected and why, so the executor doesn't relitigate them), Implementation (name real files/functions), Verification, and the Execution protocol (worktree →/merge-queue→/reflect, work autonomously). Readskills/do/SKILL.mdstep 4 and lift the Execution protocol from there rather than reconstructing it from that parenthetical — its load-bearing detail (the warm-worktree claim and its cold fallback, the slug→/merge-queueargument mapping, therelease_warm_worktreecall at/reflect) lives in the skill body, and a plan that paraphrases it drops exactly the parts the spawned executor can no longer ask about. - Do not spawn
/do <plan>. A fresh/dowould enter plan mode and stall forever waiting for a human to choose "Clear Context and Follow Plan." Instead spawn a plain execution prompt: hand over the plan and tell the session to carry it out end-to-end without pausing. This is the autonomous-executor path. - If the plan is more than a few lines, write it to a file (Write tool, to an absolute path outside the repo working tree — e.g. under
~/.claude/spawn-briefs/or amktemppath — so the worktree the spawned session creates can't shadow it and it can't be accidentally git-staged) and spawn a short prompt pointing at it, using--prompt-file <abs-path>(see Arguments) as the mechanism. Passing a multi-paragraph plan as one shell-quoted CLI arg is fragile. e.g. the file opens with →Execute this plan end-to-end, autonomously. It is the contract — don't pause for confirmation unless you hit something genuinely blocking the plan doesn't cover.withskip_permissions=true. When a brief passes on a finding that some claim is false, state the execution context the refuting check ran in, and never mark the finding settled ("do not re-derive") unless that context matches the claim's — seeorchestrator/src/orchestrator/agents/premise_refutation_guidance.py::PREMISE_REFUTATION_GUIDANCE.
/prd — resolve the subject and pre-answer the gates
- Resolve the contextual subject (
that feature,the thing we discussed) into a concrete PRD subject. /prd's value is its G1–G6 gates, and a fresh autonomous session can't ask you the questions those gates raise. So fold in everything from this conversation that bears on them — the named consumer (G1), the user-observable leaf signal (G2), substrate assumptions (G3), cross-PRD seams (G4), the premise (G6) — so the gates can be satisfied without interrogating an absent human.- Keep the
/prdwrapper (unlike/do): spawn/prd <concrete subject + folded-in context>so the gates run in the fresh session (file-spill as above if long)./prdis high-stakes and may still legitimately pause at a gate it genuinely can't resolve from the brief — that's acceptable.
When in doubt about whether a prompt is self-contained, prefer leaving it untouched and noting the ambiguity, rather than rewriting and risking corruption.
Arguments
prompt(required) — the string passed as the first positional argument toclaude. May be a slash command (e.g./unblock 123) or natural language. First resolve it per Resolving the prompt for a fresh context above — a contextual prompt typed in an interactive session must be rewritten to stand alone before it becomes this argument. The caller is responsible for ensuring the final string contains no unescaped single quotes; if it must, escape each one as'\''(close-single, escaped-single, open-single — the standard Bourne idiom). Example:it'\''s finebecomes a valid payload. It may instead be given as--prompt-file <absolute path>in the fourth position: the script then passes a short pointer prompt naming the file — never its contents, so a brief of any size stays under tmux's command-length limit — andrecord.promptholds that pointer. A relative or unreadable path exits2before anything is launched or recorded.cwd(required) — directory tocdinto before invokingclaude. Usually the project root. Must be an absolute path that exists on the host.skip_permissions(defaulttrue) — whentrue, passes--dangerously-skip-permissionsso the spawned session runs without permission prompts. Setfalsewhen the spawned session should prompt for permissions normally (e.g. interactive exploration where the human wants oversight).model(optional) — the model the spawned session runs. Pass it as theCLAUDE_SPAWN_MODELenvironment variable on thespawn-claude.shinvocation (it's an env var, not a positional, so the calling forms below are unchanged). The script forwards it verbatim toclaude --model <value>, so it accepts anything that flag does: a latest-model alias (opus,sonnet,haiku,fable) or a full model id (e.g.claude-opus-4-8,claude-sonnet-5,claude-haiku-4-5-20251001,claude-fable-5). Omit it to inherit the spawner's default model. Example: spawn a cheap triage session withCLAUDE_SPAWN_MODEL=haiku, or a heavy one withCLAUDE_SPAWN_MODEL=opus. (For arbitrary other claude flags there's a raw passthrough,CLAUDE_SPAWN_CLAUDE_ARGS, applied after--model; preferCLAUDE_SPAWN_MODELfor the common case of just picking a model.)CLAUDE_SPAWN_SELF_EXIT(optional, default off; only the exact value1turns it on) — an environment variable likeCLAUDE_SPAWN_MODEL. It stampsself_exit: trueon the session record and adds theexit-requestedmarker instruction to the result-handback trailer. Nothing ends the session on that marker unless the self-exit Stop hook (σ2 ofplans/review-all-program-tooling-prd.md) is installed.terminal_title(the target convention for programmatic callers; optional for ad-hoc interactive use) — passed via--titleto emulators that support it (gnome-terminal,konsole,xterm;kittyignores it harmlessly). Distinguishes multiple spawned sessions in the window manager, and is load-bearing for Leo-on-return and the (future) attention-manifest to identify sessions at a glance without opening each window. Follow the convention<role>:<project>#<task-id> <short-slug>—<role>is the spawned skill/command (unblock,review,prd, ...),<project>is the project_id,#<task-id>is included when the spawn is task-scoped (omit it for project-level spawns), and<short-slug>is a few hyphenated words summarizing the work. Examples:unblock:df#2085 routing-mechanism,prd:df attention-rail. Programmatic callers (e.g. escalation-watcher) should pass a convention-shaped title going forward — this is the convention new and updated call sites follow, not yet a property every existing caller satisfies. Only a genuinely ad-hoc interactive spawn may omit a title entirely.
Invocation
The skill ships with a helper script — $DARK_FACTORY_ROOT/skills/spawn/spawn-claude.sh — that handles emulator discovery, per-emulator wait-flag quirks (gnome-terminal --wait, naturally-foreground xterm/kitty), and a sentinel-based wait for detaching emulators (konsole, macOS Terminal). The background task this skill kicks off completes when the spawned claude session exits, and its exit code equals the spawned session's exit code — so the caller gets a reliable "session finished" signal without re-implementing any of that plumbing.
Bash(
command="$DARK_FACTORY_ROOT/skills/spawn/spawn-claude.sh <cwd> <skip_perms> '<title>' '<prompt>'",
run_in_background=true
)To hand over a long brief, write it to an absolute path outside the repo and pass --prompt-file in place of the prompt (see the prompt argument above):
Bash(
command="$DARK_FACTORY_ROOT/skills/spawn/spawn-claude.sh <cwd> <skip_perms> '<title>' --prompt-file <abs-path>",
run_in_background=true
)To pin the spawned session's model, prefix the command with CLAUDE_SPAWN_MODEL=<alias-or-id> (see the model argument above) — nothing else changes:
Bash(
command="CLAUDE_SPAWN_MODEL=haiku $DARK_FACTORY_ROOT/skills/spawn/spawn-claude.sh <cwd> <skip_perms> '<title>' '<prompt>'",
run_in_background=true
)Where:
<cwd>is an absolute path that exists.<skip_perms>is the literal stringtrueorfalse.<title>is the terminal-window title — programmatic callers should follow the<role>:<project>#<task-id> <short-slug>convention documented in Arguments above (existing call sites are migrating to it; not all have yet). Pass''only for a genuinely ad-hoc interactive spawn with no meaningful title.<prompt>is the literal argument passed toclaude. Wrap in single quotes; escape any inner single quote as'\''.run_in_background=trueis essential — the script blocks until the session exits, which may be hours. Foreground would tie up the caller.
If $CLAUDE_TERMINAL_CMD (preferred) or $ESCALATION_TERMINAL_CMD (legacy) is set, the script honours it; otherwise it discovers an emulator in $PATH. Do not re-implement discovery in the skill — the script is the single source of truth.
Sibling mode (CLAUDE_SPAWN_MODE=sibling)
By default (CLAUDE_SPAWN_MODE unset, or any value other than sibling) a spawned session's parent-of-record is its direct spawner — this invocation's own inherited identity — and spawn-claude.sh blocks until the spawned session exits, exactly as described in Verification below.
Set CLAUDE_SPAWN_MODE=sibling when a session should hand off to a fresh session that runs alongside it rather than beneath it — see Generalized handoff pattern below. Sibling mode changes two things:
- Parent-of-record. The child's parent is set to this invocation's own parent (the shared ancestor it was itself spawned under), not this invocation's own session id — so a chain of handoffs stays flat under one shared ancestor instead of nesting one level deeper on every handoff. A spawner with no parent of its own (a human-launched root) produces a child with no parent either (null → root).
- Fire-and-forget.
spawn-claude.shreturns immediately once the child is launched, with no blocking wait for it to finish — the exit-code contract in Verification does not apply to a sibling spawn's own return code. The child is launched detached (setsidon the emulator branches that need it, and only where the host can actually run it — the macOSTerminallane deliberately uses none, sinceopen's LaunchServices handoff already detaches it and stock macOS does not shipsetsid; any other launcher on such a host degrades to a plain background job rather than a dropped launch — plus its stdio redirected off this script's own output) so it survives the spawner's process exiting. Because of this, sibling handoff pairs best with a launcher that already keeps the window alive independent of the launching client — a daemon-owned emulator (gnome-terminal,konsole) or the tmux lane ($CLAUDE_SPAWN_BACKEND=tmux, Fleet Cockpit C6) — rather than one whose only lifetime is the launching terminal's own session. - The join is not the exit code. Since there is no blocking wait, a caller cannot join a sibling spawn the way it would a normal one. Read the session-registry record (best-effort refreshed to
runningonce launched) and, authoritatively, the child's own result.md once it writes one.
What a spawned session inherits
spawn-claude.sh unsets the entire CLAUDE_SPAWN_* namespace inside the payload that execs claude, then re-exports only the values it computed for that child. A spawned session therefore never carries its own launch parameters forward, and cannot re-serve them to a session it spawns in turn.
Exactly five vars are visible to the spawned session:
| Var | What it is for |
|---|---|
CLAUDE_SPAWN_SESSION_ID |
The child's own session-registry slug. Its SessionStart/Notification/Stop hooks key their record writes on this, so they converge on the record this spawn already created rather than forking a second one — but only while it is this session's record: the first hook event that can PROVE it is this session's own binds its Claude Code session_id into the record, and a nested claude started from inside this session by other means (a Bash-tool claude -p, an agent shelling out) inherits the variable but is detected — by its differing session_id once this record is bound, or by CLAUDE_SPAWN_OWNER_PPID before it is — and gets its own record: parented to this session, without claiming this session's window, and reapable once that nested process exits (reaping also needs the non-terminal heartbeat TTL to elapse, and where /proc is unavailable the liveness pid degrades to the terminal's session leader, which outlives the nested claude). |
CLAUDE_SPAWN_PARENT_ID |
The child's parent-of-record — the direct spawner, or the shared ancestor in sibling mode. Absent when the child is a root (the export is emitted only for a non-empty parent), not present-but-empty. |
CLAUDE_SPAWN_WM_TITLE |
The exact window-title marker handed to the terminal emulator, so the child's own hook can resolve its live window id via wmctrl -l. Set only when a non-empty <title> was passed. |
CLAUDE_SPAWN_RESULT_FILE |
Where the child writes its outcome — see Result-handback. |
CLAUDE_SPAWN_OWNER_PPID |
The pid of the payload shell that runs claude, i.e. the owning session's direct parent. The hooks compare it against the live process tree to tell this session from a nested claude that merely inherited CLAUDE_SPAWN_SESSION_ID, during the window before the record is bound. Unconditional — it is computed inside the payload and depends on neither the registry nor the title. |
Three of the five are gated on the session-registry record dir, so on a registry fault none of CLAUDE_SPAWN_SESSION_ID / CLAUDE_SPAWN_PARENT_ID / CLAUDE_SPAWN_RESULT_FILE is set. In particular CLAUDE_SPAWN_RESULT_FILE is then absent rather than pointing at the spawner's own file, so a child can never overwrite its parent's outcome record. CLAUDE_SPAWN_WM_TITLE is gated only on a non-empty <title> — it has nothing to do with the registry, only with what title was handed to the emulator — so it is still exported on that path, and CLAUDE_SPAWN_OWNER_PPID is gated on nothing at all.
Because the namespace is cleared unconditionally, "not set" always means absent, never inherited-from-the-spawner. A session can therefore read the presence of one of these vars as a fact about itself.
Inheritance alone is not ownership, though. Clearing the namespace means a child launched through this script gets its own fresh values, but a claude started from inside a spawned session by any other route — a Bash-tool claude -p, an agent shelling out — still inherits the parent's, and would otherwise write its lifecycle onto the parent's record. Two tokens tell the two apart, covering different halves of the lifetime. Once the record is bound, the hook stdin session_id does: the hooks bind that token into the record on the first proven-owner adoption and treat a later, differing one as an inheritor to be forked off. Adoption alone does not bind — adopting is fail-soft, a binding is permanent — so an event whose ownership is merely unproven (a session spawned before CLAUDE_SPAWN_OWNER_PPID existed, any record already live when this landed, or a platform with no /proc) writes to the record it adopted but leaves it unbound, open for its true owner, rather than letting whoever arrived first claim it. Before it is bound — between this script's launching write and the spawned session's own first SessionStart, a window measured at ~27s median and ~141s at p90 over the live fleet — there is no binding to compare against, so CLAUDE_SPAWN_OWNER_PPID does instead: this script exports the pid of the payload shell from inside the payload itself, and since claude is invoked there without exec, the owning session's direct parent is always exactly that pid while a nested claude has its agent's Bash-tool shell instead. Note that neither is an ancestry test — a nested claude is also a descendant of the original launcher, so PID lineage cannot discriminate at all. Both probes fail soft toward adopting: ownership that can be neither proved nor disproved never forks. The one mismatch that is not an inheritor is a /clear, which re-mints session_id inside the same process — the hooks recognise it by the SessionStart-only source field, trusting only clear and compact there (neither has a command-line spelling, so only the process already holding the session can report one; resume is excluded because --resume/--continue make a fresh nested process report it as well), and re-bind the record rather than forking it, so clearing a spawned session does not split its row — while a spawned session resumed in place merely forks onto its own record rather than inverting ownership.
Everything else is a per-launch INPUT: CLAUDE_SPAWN_MODE, BACKEND, MODEL, CLAUDE_ARGS, SELF_EXIT, TMUX_SESSION, ROLE, PROJECT, TASK_ID, ESCALATION_ID, and Fleet Cockpit's SKIP_PERMS/SCRIPT. Each is consumed by the invocation it is passed to and then removed from the child's environment. Callers must set these explicitly on every spawn — they no longer propagate transitively to grandchildren the way ambient inheritance used to carry them. Identity is not lost by this: ROLE/PROJECT/TASK_ID/ESCALATION_ID are stamped onto the session-registry record by the launching write, which happens before and outside the payload, so they remain readable from record.json; they simply no longer travel in the environment.
Why the namespace is cleared wholesale rather than by an exemption list: a session launched with CLAUDE_SPAWN_CLAUDE_ARGS="--resume <its own id>" (fleet crash-recovery) would otherwise re-serve that flag as apparent caller intent on its next /spawn, so a session meant to be a fresh sibling came up as a live resume of its spawner (2026-08-11). Any allowlist reintroduces exactly that failure mode for the next var nobody remembers to list.
Callers do not need to scrub the namespace themselves — prefixing an invocation with env -u CLAUDE_SPAWN_... is unnecessary, and a caller that sets a var deliberately still has it honoured for that launch.
Verification
The background task's completion is a reliable liveness signal: it tells you the spawned session's process is gone, present vs. died-silent. As of Attention Rail T5, exit codes are documented as liveness-only — they are not, and should not be treated as, the semantic outcome channel. That channel is an explicit file the session writes: see Result-handback (result.md) below. All codes below are still returned unchanged (nothing here is a breaking change); only their meaning narrows to "was the process alive, and how did it stop," not "did the work succeed." Script-level exit codes the caller should distinguish:
| Exit code | Meaning |
|---|---|
0..125 |
The spawned claude session's own exit code. Liveness only — a 0 means the process exited cleanly, not that the work succeeded or is complete; check result.md for the outcome. |
126 |
No terminal emulator found — prompt the user for $CLAUDE_TERMINAL_CMD and retry. Suggest they export it in their shell profile for future sessions. |
127 |
Launcher failed to start the session (the emulator binary errored before running the payload — no sentinel was ever written, or the sentinel never settled to a numeric exit code within the launch grace). Surface the error to the caller. |
129 |
Terminal window closed while the session was still alive (SIGHUP reached the running session) — or a session that had already finished cleanly raced its own window teardown and still surfaced as 129 (see "The 129-on-clean-exit race" below). Inconclusive by construction: never read 129 as "the session failed" or "the work was lost" — read result.md. |
144 |
Claude never started — no new transcript appeared under ~/.claude/projects/<encoded-cwd>/ and no claude process was detected within the started-grace window; the background started-watchdog marked the session-registry record failed-to-start and emitted a loud caller-visible line on the spawn's stderr. This is the silent-no-transcript hang (2026-07-06 incident), now surfaced within grace instead of hanging. Additive; all existing codes retain their meaning. |
2 |
Bad usage — caller bug, not user-recoverable. Includes a relative or unreadable --prompt-file path. |
The transcript path used by the 144 check encodes the session's cwd by replacing every / and . with -, matching session_registry.transcript_path_for_cwd byte-for-byte — e.g. /home/leo/src/dark-factory → ~/.claude/projects/-home-leo-src-dark-factory/. The started-grace window defaults to ~90s and is tunable via $SPAWN_STARTED_GRACE_SECS.
The 129-on-clean-exit race. 129 is not a reliable "killed mid-session, work possibly lost" signal, even though that's what it originally meant. spawn-claude.sh's payload arms an EXIT trap (captures claude's real exit code via ${ec:-$?}) and a HUP trap (converts a window-close SIGHUP into exit 129) before invoking claude. A clean exit — e.g. the user hits Ctrl-C (or the session simply finishes) at almost the same moment the terminal window is torn down (the emulator closing the window right as the payload shell is unwinding) — can still race the HUP trap: it wins the race and overwrites the EXIT trap's capture of the real, already-successful exit code, so a session that completed its work cleanly surfaces as 129 at the parent anyway. This has been observed in practice, not just theorized. The fix is not to chase a tighter race window; it's to stop reading exit codes for outcome at all — that's exactly what result.md exists for.
Known limitations of the 144 check:
- Concurrent same-cwd spawns. The transcript directory is keyed on
cwd, not on the individual session — Claude Code writes every session for a givencwdinto the same directory. If a second, genuinely healthy spawn for the samecwdwrites its own new transcript while a sibling spawn is truly failing to start, the healthy sibling's file is indistinguishable from the failing spawn's own evidence, so the failing spawn is never flagged (a false negative). The detector is exact for the common single-spawn-per-cwd case, including the motivating 2026-07-06 incident, but treat coverage as reduced for a concurrent same-cwd fleet. - Detached launchers can false-flag a live-but-slow session. For a detached launcher (
konsole, or a custom$CLAUDE_TERMINAL_CMD), the transcript probe is the only positive evidence — a liveclaudeprocess can't be observed once its launcher detaches. A real session that is merely slow to write its first transcript (heavy load, cold cache) can still be flagged144even though it is alive and keeps running detached. Treat a144on a detached launcher as "no evidence seen within grace," not as confirmation the session is dead, and raise$SPAWN_STARTED_GRACE_SECSfor callers on slow or loaded hosts.
If you want to confirm the spawned session is alive mid-run, ps -ef | grep claude works. The background task itself is the canonical liveness signal — don't poll it via TaskGet in tight loops; just wait for its completion notification.
Result-handback (result.md)
Exit codes (above) only tell you the process is gone and roughly how it stopped — never what happened. The semantic outcome channel is an explicit file the spawned session writes before it ends:
spawn-claude.shallocates<record-dir>/result.md— the same session-registry record directory captured asSESSION_RECORD_DIR— and exports its path into the spawned session's own environment as$CLAUDE_SPAWN_RESULT_FILE. The identical path is stored in the session-registry record'sresult_filefield, so a parent reading the record never has to recompute or guess the path. It is one of the five vars a spawned session may read from its own environment — see What a spawned session inherits.- The prompt handed to the spawned session gets a standard trailer appended, asking it to write — before ending, whether it finishes, hands off, or gets blocked — a small structured markdown header followed by a few sentences of prose context. The header's lines are
outcome: done|blocked|abandoned|handed-off|in-progress,changed(commits/branches/task ids touched), andaction_needed(what a human or parent should do next).in-progressmarks a checkpoint written before the work is done; the session rewrites the file with a final outcome before it ends. The trailer also asks the session to run/reflectbefore it ends, and underCLAUDE_SPAWN_SELF_EXIT=1to write theexit-requestedmarker into its record dir as its last action. - This is best-effort: nothing in
spawn-claude.sh, and nothing the trailer asks for, blocks the session's own exit. A parent joining a completed spawn (e.g. escalation-watcher on a/spawnbackground task's completion) should readrecord.result_file(equivalently<record-dir>/result.md) for the authoritative outcome, and fall back to exploring the worktree/task only when the file is absent, empty, or unparsable. - Fail-soft, matching the registry's own fail-soft contract: if the session-registry
launchingwrite itself faults (missingpython3, an unwritable fleet root, etc.),SESSION_RECORD_DIRis empty and both the env export and the prompt trailer are cleanly skipped — no bogus/result.mdpath is ever exported, referenced, or created. Skipped means absent, not inherited: the payload's namespace unset has already run, so a spawner's own$CLAUDE_SPAWN_RESULT_FILEcannot survive into the child on this path and be written over. The exit-code contract above is unaffected either way. - A future
Stop/SessionEndhook (Attention Rail T6) may add a fallback stub-write for a session that skips the trailer's instruction, but this protocol does not depend on T6 landing — the prompt trailer plus the session's own best-effort write is already a complete signal, if occasionally missed.
Generalized handoff pattern (author→execute, investigate→fix)
A session that has produced a durable artifact — a committed PRD, a distilled fix plan, anything the next phase can pick up from disk without this session's own context — can hand off to the next phase instead of blocking to babysit it:
- Spawn the next-phase session as a sibling (
CLAUDE_SPAWN_MODE=sibling, see Sibling mode above), with a self-contained prompt pointing at the durable artifact (an absolute path, a commit sha, a task id — never a contextual reference back to this conversation; see Resolving the prompt for a fresh context above). - Write this session's own result.md:
outcome: handed-off,changednaming the artifact just produced, andaction_needed/prose pointing at the spawned sibling. - Exit cleanly — do not wait for the sibling to finish.
Worked example: /prd author mode, once the PRD is committed, may spawn /prd decompose <path> as a sibling and hand off rather than continuing inline (see skills/prd/references/author-mode.md Stage 11). The same shape applies to an investigate→fix handoff: a session that distills a root-cause and fix plan spawns the implementer as a sibling pointed at the plan, instead of implementing the fix itself in an already-long-running investigation session.
When NOT to use
- In-session work — if the work belongs in your current conversation, do it directly or delegate to a sub-agent via the
Agenttool. Don't spawn a new terminal just to get a sub-task done. - Non-Claude commands — for arbitrary shell commands in a new window, call Bash directly with
run_in_background=true. This skill is specifically for launchingclaudesessions. - Inside skills that forbid spawning —
escalation-watcher-autoexplicitly prohibits any form of terminal spawning, including this skill. Respect the hosting skill's hard constraints.