spawn-session
Spawns a background Claude Code helper in the project or another folder with the configured Remote Control and the boot-written helper system prompt that a hand-built `claude --bg` omits, watches it until idle, and relays its report to the operator. Use when the operator says "spawn a helper", "spawn a new session", "run this in a background session", asks for a new session with `--model` or `--effort`, asks to switch a running helper's model or effort, or names `/spawn-session`.
- License 许可
- MIT
- 所属仓库
- gtapps/hermitd
- 最近更新
- 2026-11-12
- Stars
- ★ 84
- Forks
- 16
01 前言 Front Matter
- name
- spawn-session
- description
- Spawns a background Claude Code helper in the project or another folder with the configured Remote Control and the boot-written helper system prompt that a hand-built `claude --bg` omits, watches it until idle, and relays its report to the operator. Use when the operator says "spawn a helper", "spawn a new session", "run this in a background session", asks for a new session with `--model` or `--effort`, asks to switch a running helper's model or effort, or names `/spawn-session`.
02 资源文件 3
- 参考文档reuse-spawned-helper.md2.0 KB
- 参考文档switch.md3.3 KB
- 参考文档untrusted-folder.md1.6 KB
03 SKILL.md 正文
Spawn Session
Launch a background Claude Code session in a folder, subscribe to its idle
notice, and relay the report through /hermitd:watch.
Usage
/hermitd:spawn-session <prompt-or-/skill> [--cwd <abs-dir>] [--worktree] [--name <n>] [--model <m>] [--effort <e>] [--background <abs-file>] [--task <id>] [--strict-mcp-config]
/hermitd:spawn-session --switch <n> --record <T-id> [--model <m>] [--effort <e>]
From <dir>, the launch folder (<abs>, the hermit project root, unless
--cwd names another), that composes:
claude --bg --name <n> [--worktree <n>] [--permission-mode <p>] [--no-chrome] [--remote-control <n>] [--model <m>] [--effort <e>] [--mcp-config <dir>/.mcp.json] [--strict-mcp-config] --append-system-prompt-file <abs>/.hermit/state/helper-system-prompt.md '<prompt>'
--no-chrome is present on fresh launches when config.chrome is false (absent means false). Never add it to claude --bg --resume: resume must remain flagless to retain saved options.
--worktree <n> is present only when the operator passed --worktree, and
gives the helper its own worktree of <dir> from the start.
--remote-control <n> is present when config.json's remote is true and
absent otherwise; --model and --effort only when the operator passed them.
--mcp-config <dir>/.mcp.json and --strict-mcp-config only when the operator
passed --strict-mcp-config: both flags when <dir>/.mcp.json exists, and
--strict-mcp-config alone when it does not. Strict mode drops plugin and
user-scope MCP servers from the helper. A background session cannot answer
the project-server approval dialog, and a folder named by --cwd brings its
own .mcp.json to that dialog.
A helper report counts only when its sender's session name equals the target of a live peer-idle registry entry; the epoch-suffixed helper name is what distinguishes one spawn from a reused name. The helper never writes tasks/; the resident records progress and results after that sender check.
Six limits sit on that command:
- The helper starts in
<dir>. In a repository's main checkout, Claude Code refuses its Edit and Write until it callsEnterWorktree: theworktree.bgIsolationsetting,"worktree"by default (https://code.claude.com/docs/en/settings-reference#worktree-bgisolation)."none"in that project's settings lets helpers edit the checkout directly, on whatever branch the resident has checked out; this skill never sets it. A linked worktree is edited in place under either value, and Bash git commands are not fenced by the setting. Outside a git repository the block still applies, so a non-git<dir>leaves the helper read-only. - Any file the helper must Read is passed as an absolute path in the prompt,
spelled as an
@<abs-path>mention so Claude Code injects it at launch; a worktree does not carry the hermit's untracked state. <abs>/.hermit/state/helper-system-prompt.mdis written at boot and gated likeconfig.json. Pass it as--append-system-prompt-file(a system-prompt file is not expanded, so@<abs-path>mentions still belong in the prompt). The appended text reaches the helper's main conversation and forks, not its non-fork subagents.--permission-mode <p>fromconfig.json'spermission_modekeeps the helper in this session's permission class, which is what lets its idle notice reach here rather than being held for an operator who is not watching.config.jsonaccepts one value the CLI has no choice for,default, so it andnulland an absent key all mean: leave the flag off entirely and let the helper take the box default.scripts/hermitd-start.tsresolvesdefaultandnullthe same way; match that rather than inventing a second answer. It does not agree on an absent key, which it reads asautorather than as no flag.bypassPermissionsis the one value that does not pass through: it becomes--permission-mode autobecause a helper has no approval surface of its own, andautois the only mode that stays unattended behind a gate.- The prompt is one single-quoted argument. An apostrophe in it ends the quote,
so replace every
'with'\''before composing. Anything after the closing quote is a second command the operator never asked for. - The launch is not pre-approved, and what the operator sees depends on the
mode they run in. On
auto, the shipped default, the classifier decides and no prompt reaches them on any channel. OnacceptEditsormanualthe native approval is relayed to their DM and is allow-once, so every spawn asks again. OnbypassPermissionsthere is none. Say what is about to be spawned before running it either way: it is the only thing that makes the launch legible when an approval does arrive, and the only record when none does.
Use only the launch options documented here. Never add bypass flags, tool preapprovals, or settings overrides to widen the helper's permissions. If launch or execution is blocked, report the blocker; do not retry through a script, alternate invocation, or weaker permission mode. The exceptions are § Follow-ups, "Resume with new instructions", for an operator follow-up, § Switch a running helper for a model or effort switch, and untrusted-folder.md for a refused workspace.
Commands
helper-switch:bun ${CLAUDE_PLUGIN_ROOT}/scripts/helper-switch.tstrust-workspace:bun ${CLAUDE_PLUGIN_ROOT}/scripts/trust-workspace.tstask-note:bun ${CLAUDE_PLUGIN_ROOT}/scripts/task.ts note .hermittask-list:bun ${CLAUDE_PLUGIN_ROOT}/scripts/task.ts list .hermit
Plan
-
Parse
--cwd,--worktree,--name,--model,--effort,--background,--task, and--strict-mcp-configfrom the invocation.--cwdmust be an absolute path to an existing directory.--backgroundmust name an existing absolute file; append it to the prompt as an@<abs-path>mention.--task <id>is resolved in step 2 before any launch. Remaining text is the prompt. Empty prompt: stop with a one-line ask for the work to run.- When
--nameis omitted, derive<n>from the prompt: drop a leading/and any<plugin>:namespace, lowercase, replace every non-[a-z0-9]run with-, keep the first five nonempty tokens joined by-, cap the slug at 40 characters, trim any leading or trailing-, then append-plus the full epoch (date +%s). The trim is what keeps a prompt like#220 fix the parserfrom producing a name the launch command reads as a flag. The epoch is not truncated because the watch registry keys on the full name. With--worktreeit also matters becauseclaude --worktree <n>silently reuses an existing.claude/worktrees/<n>, its branch and uncommitted state included, so a repeated name is a wrong-branch start with no error. If no token survives the slug issession, which is what makes the fallbacksession-<epoch>. Example:/tackle-issue PROP #220becomestackle-issue-prop-220-1788889689. <m>/<e>are omitted when the operator does not name them, so the helper takes the box defaults.
- When
-
Resolve
<abs>withgit rev-parse --show-toplevelrather than reading the Bash tool's working directory, which persists across calls and can sit in a subdirectory.<dir>is--cwdwhen given, else<abs>. No further check on<dir>: a folder the CLI refuses is reported with the CLI's own message, except for the untrusted-folder branch in step 3. With--worktree, rungit -C <dir> rev-parse --verify HEAD; on failure, refuse with one line before composing any launch:<dir>is not a git repository or has no commits; make an initial commit, then retry. An unborn HEAD makes the background launch report success and then crash-loop on worktree creation. When--taskwas passed, validate<id>withtask.ts list <abs>/.hermit --id <id> --json; refuse a missing or closed record before launch. Append<abs>/.hermit/tasks/<id>.mdto the helper prompt as an absolute background path. Read<p>from<abs>/.hermit/config.json(permission_mode), dropping the flag fordefault,nullor an absent key, mappingbypassPermissionstoauto(Six limits), and passing every other value through unchanged. Readremotefrom the same config and include--remote-control <n>only when the key is present andtrue;false,nulland an absent key all leave the flag off, which is the resident session's own answer for that config. When the operator passed--strict-mcp-config, include--mcp-config <dir>/.mcp.json --strict-mcp-configif<dir>/.mcp.jsonexists, and--strict-mcp-configalone if it does not. Check that<abs>/.hermit/state/helper-system-prompt.mdexists; boot writes it. If it is missing, refuse with one line: restart with hermitd-start so boot writes the helper system-prompt file. -
cd <dir>as its own Bash call, then run the command in Usage as the next one, so the launch stands alone in the transcript and in any approval that does reach the operator. With--cwd, thencd <abs>as the next call whatever the launch returned: the task calls below resolve the relative.hermitagainst the Bash tool's persisted directory. Print the returned bg id andclaude logs <id>,claude attach <id>,claude stop <id>hints. For aWorkspace not trusted. Runrefusal, follow untrusted-folder.md. Home-directory and unresolvable refusals are reported without that branch. If the spawn is declined or fails, stop; do not watch, open a record, or patch a Decision. After every successful launch, read the full session id<sid>once:claude agents --json | jq -r --arg id <bg-id> '.[] | select(.id==$id) | .sessionId'The printed bg id is only the first 8 characters of
<sid>and is not a resume handle. An empty result means no<sid>: say so, write the lines below without(<sid>), and continue to step 4. Without--task, inside an open record's turn, appendHanded to helper <n> (<sid>).as a progress note by piping it intobun ${CLAUDE_PLUGIN_ROOT}/scripts/task.ts note .hermit <T-id>, where<T-id>is that open record's id, not the bg id (the note is timestamped for you). After the handoff note, bind the record withbun ${CLAUDE_PLUGIN_ROOT}/scripts/task.ts note .hermit <T-id> --owner helper:<n>. Oninvalid_owner(a custom name outside[A-Za-z0-9._-]{1,64}) orowner_busy, leave it resident-owned and tell the operator why. With--task <id>, open or reuse a helper-work task with--dedupe-key "helper:<task-id>", requester from the current authorized turn, and a title naming the helper work. This record holds the helper's report, not the proposal's implementation result. NoteHanded to helper <n> (<sid>); helper record <helper-task-id>.on the source task, and bind the helper-work record with--owner helper:<n>. A repeated launch for the same source task reuses this helper record. -
Invoke
/hermitd:watch session <n> "<first 40 chars of the operator prompt>" --id <bg-id>. When the watch reports the helper blocked on itswaitingFor, add nothing: the watch's helper poll announces it in chat. Whenever step 3 bound or opened a record, also pass--record <T-id>. That skill owns the subscription (watch/session-watch.md§ Starting a session watch) and the idle-notice relay (watch/notices.md§ Handling idle notices); do not re-implement either. When it declines the subscription, pass on the reason it gives rather than asserting one: the helper may still be running, or it may have finished its first turn before the subscription landed. Either way, give theclaude logs <id>id as the way to check on it. When the decline isNo session named <n> is reachable from hereand the launch printed a bg id, runclaude logs <id>and show the operator the last few ANSI-stripped lines; name the state those lines show (a boot dialog it cannot answer, a crash, a finished turn) rather than one they do not. A declined subscription writes no registry entry, so no report can ever reach the record opened in step 3: cancel it with the decline reason (task.ts cancel .hermit <T-id> --actor <requester> --reason-stdin) and say whether the tail leaves the helper running unwatched or stuck, rather than leaving a commitment waiting on a report that cannot arrive.
Follow-ups
Send follow-ups by SendMessage with notify_when_idle: true, then read
watch § Registering an existing idle subscription with the sent text. The watch relay
retries a blocked or declined follow-up once with that stored request.
Resume with new instructions
Inputs: helper name, instructions, watch note and flags, and optional blocked-reply input from watch's helper poll handler. Return the outcome to the caller so it can handle a resume that could not start.
- Read the helper's row in
claude agents --json. Ready meansstatusisidleandstateis neitherworking,blockednorscheduled. With the blocked-reply input, also acceptstatuswaitingwithstateblocked: a helper stuck on a prompt or question, which a message cannot reach. Take itsidas the bg id and itssessionIdas the resume handle. Not ready or no row: end without resuming; never stop a busy helper. - Run
claude stop <bg-id>, then in one bounded Bash call pollclaude agents --jsonevery 2s for up to 30s until no row has thatid. Still listed at the deadline: end without resuming and report that outcome. Then callTaskStopon the old watch entry'spoll_task_idwhen present; an error means it already exited. - From the project root, run
claude --bg --resume <sessionId> '<instructions>'with no other flag. Apply the launch limits''\''quoting rule to every apostrophe in the instructions. The saved options include the helper system prompt; extra flags start a copy instead. Launch failure or any output other than a normal resume (for example "started a copy"): do not try again; tell the operator what it printed and end with that outcome. - Remove the helper's old watch entry if still live and write the registry,
then invoke
/hermitd:watch session <name> "<note>" --id <bg-id>plus the given flags. Use the resumed session's bg id (the first 8 characters of its session id). Do not store these prompt instructions asfollowup: they were delivered by resume, not by message. If the watch declines, return that the resumed helper runs unwatched; a launched resume counts as reached even so.
Switch a running helper
For --switch, Read switch.md and follow that procedure instead of
the launch Plan. It preserves the helper's conversation, name and thread binding.
Stuck helper
claude logs <id>, claude stop <id>, and the watch expiry notice. Never tmux.
Idle is not stuck: watch's idle-notice relay leaves an idle helper running,
except for § Follow-ups, "Resume with new instructions", and § Switch a running helper.
A boot dialog in claude logs <id> is handled as in this terminal: relaunch
with the option that makes the dialog moot when one exists, otherwise give
the operator claude attach <id> (for a hermit in Docker,
docker exec -it <container> claude attach <id>), because Remote Control is
not connected before boot.
04 同仓库更多 Skills
dream
Nightly memory maintenance from transcript ground truth. Verifies, enriches and tidies auto-memory and topic pages with snapshot validation and undo. Also handles 'undo the dream'.
when-done-switch-to
Arm a deferred model or effort switch. Use for "when done switch to", "after this drop to sonnet", or "switch model when idle".
weekly-review
Generate the weekly review report for the current ISO week. Writes to .hermit/compiled/review-weekly-YYYY-Www.md (dev-facing detail) and sends a plain-language channel summary (Delivered / Decisions / Waiting on you / Spend). Runs every Sunday at 23:00 via routine.
watch
Background events via the config watch monitor or ad-hoc Monitor tool. Quiet watches cost zero tokens.
watch-activate
Activate the config watch monitor.
voice-notes
Transcribe and respond to voice notes. Activates when any channel message carries an audio attachment, or when asked to transcribe an inbound voice message.