{"record":{"id":"0ea06e30174c06c5","repo":"can1357/oh-my-pi","slug":"vibe-session-record-id-is-dead-spawn-a-new-o","errorCode":null,"errorMessage":"Vibe session \"${record.id}\" is dead. Spawn a new one with vibe_spawn.","messagePattern":"Vibe session \"(.+?)\" is dead\\. Spawn a new one with vibe_spawn\\.","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/vibe/runtime.ts","lineNumber":939,"sourceCode":"\t\t\t\tif (!record.terminalPersisted) {\n\t\t\t\t\tthrow new ToolError(\"Vibe parent session changed before spawn failure could be persisted.\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.#records.delete(key);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Send a message to a worker. Mid-turn and streaming → steering; mid-turn\n\t * otherwise → queued for the next turn; idle/parked → starts a new\n\t * background turn immediately.\n\t */\n\tasync send(session: ToolSession, args: { session: string; message: string }): Promise<VibeSendOutcome> {\n\t\tconst scope = this.ownerScope(session);\n\t\tconst record = this.#record(scope, args.session);\n\t\tif (record.state === \"dead\") {\n\t\t\tthrow new ToolError(`Vibe session \"${record.id}\" is dead. Spawn a new one with vibe_spawn.`);\n\t\t}\n\t\tconst message = args.message.trim();\n\t\tif (!message) throw new ToolError(\"Message must not be empty.\");\n\t\tconst registered = this.#registeredAgent(record);\n\t\tif (AgentRegistry.global().get(record.id) && !registered) {\n\t\t\tthrow new ToolError(`Vibe session \"${record.id}\" no longer resolves to this parent session.`);\n\t\t}\n\n\t\tif (record.turn) {\n\t\t\tconst live = registered?.session;\n\t\t\tif (live?.isStreaming) {\n\t\t\t\tawait live.steer(message);\n\t\t\t\trecord.lastActivityAt = Date.now();\n\t\t\t\treturn { id: record.id, mode: \"steered\" };\n\t\t\t}\n\t\t\trecord.queue.push(message);\n\t\t\trecord.lastActivityAt = Date.now();\n\t\t\treturn { id: record.id, mode: \"queued\" };","sourceCodeStart":921,"sourceCodeEnd":957,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/vibe/runtime.ts#L921-L957","documentation":"VibeSessionRegistry.send() refuses to deliver a message to a vibe worker whose record is in the 'dead' state. A dead record means the worker spawned but terminally failed (crashed, was killed, or its spawn/turn failed permanently) and can no longer accept messages. The error explicitly directs the caller to spawn a replacement worker with vibe_spawn.","triggerScenarios":"Calling send(session, { session: '<id>', message }) with the id of a vibe session whose record.state === 'dead' — e.g. after the worker process crashed, was killed via vibe_kill, or a previous turn failed terminally.","commonSituations":"Agent workflow sends messages to a worker that crashed earlier in the run; reusing a stored session id from a previous task after the worker died; a worker killed due to error/timeout then addressed again by a queued follow-up message.","solutions":["Spawn a new worker with vibe_spawn (optionally passing the same name to get a successor id) and resend the message to the new session.","Check the session's lastActivity (e.g. 'spawn failed' / kill reason) via vibe_status to understand why it died before respawning.","If the death was caused by a bad prompt or model config, fix the prompt/cli/model args before respawning.","Update orchestration code to handle dead sessions by respawning instead of retrying send."],"exampleFix":"// before\nawait registry.send(session, { session: deadId, message: 'continue' });\n// ToolError: Vibe session \"x\" is dead.\n\n// after\nconst spawned = await registry.spawn(session, { cli: 'codex', prompt: 'continue the work' });\nawait registry.send(session, { session: spawned.id, message: 'continue' });","handlingStrategy":"validation","validationCode":"// Look up the record's state before sending\nconst status = await registry.status(session, { sessions: [id] });\nif (status.find(s => s.id === id)?.state === 'dead') {\n\t// respawn instead of sending\n}","typeGuard":"function isAliveRecord(record: VibeRecord): boolean {\n\treturn record.state !== 'dead';\n}","tryCatchPattern":"try {\n\tawait registry.send(session, { session: id, message });\n} catch (err) {\n\tif (err instanceof ToolError && err.message.includes('is dead')) {\n\t\tconst spawned = await registry.spawn(session, { cli, prompt: message });\n\t\treturn spawned;\n\t}\n\tthrow err;\n}","preventionTips":["Check vibe_status before messaging a worker after any failure.","Treat worker ids as single-task: never reuse an id across tasks.","Build respawn-on-death logic into orchestrators instead of retrying send.","Inspect lastActivity to learn why the worker died before respawning."],"tags":["vibe","dead-session","worker-lifecycle"],"backgroundTag":"dead-session-reference","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}