{"record":{"id":"e173cbc655903758","repo":"can1357/oh-my-pi","slug":"vibe-record-id-cli-record-cli-turn-turnin","errorCode":null,"errorMessage":"[vibe:${record.id} cli=${record.cli} turn=${turnIndex}] turn failed: ${reason}","messagePattern":"\\[vibe:(.+?) cli=(.+?) turn=(.+?)\\] turn failed: (.+?)","errorType":"exception","errorClass":"VibeTurnError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/vibe/runtime.ts","lineNumber":1445,"sourceCode":"\t\t\t\t\t\t? await runSubprocess(await this.#buildSpawnOptions(session, record, message, signal, onProgress))\n\t\t\t\t\t\t: await runSubagentFollowUpTurn({\n\t\t\t\t\t\t\t\tid: record.id,\n\t\t\t\t\t\t\t\tagent: record.agent,\n\t\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\t\tdescription: `vibe ${record.cli} session`,\n\t\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\t\tonProgress,\n\t\t\t\t\t\t\t\teventBus: session.eventBus,\n\t\t\t\t\t\t\t\tsubagentEventBus: session.subagentEventBus,\n\t\t\t\t\t\t\t\tartifactsDir: session.getSessionFile()?.slice(0, -6),\n\t\t\t\t\t\t\t});\n\t\t\t\t\treturn await this.#settleTurn(session, manager, record, turn, ownJobId, turnIndex, result);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof VibeTurnError) throw error;\n\t\t\t\t\tawait this.#finishTurn(session, manager, record, ownJobId);\n\t\t\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\t\t\trecord.lastActivity = firstLine(`turn failed: ${reason}`);\n\t\t\t\t\tthrow new VibeTurnError(\n\t\t\t\t\t\t`[vibe:${record.id} cli=${record.cli} turn=${turnIndex}] turn failed: ${reason}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ id: `${record.id}-t${turnIndex}`, agentId: record.id, ownerId: record.ownerId },\n\t\t);\n\t\tturn.jobId = jobId;\n\t\trecord.turn = turn;\n\t\treturn jobId;\n\t}\n\n\t/** Post-turn bookkeeping shared by success and failure paths: clear the in-flight turn, flush the queue. */\n\tasync #finishTurn(\n\t\tsession: ToolSession,\n\t\tmanager: AsyncJobManager,\n\t\trecord: VibeRecord,\n\t\tsettledJobId: string,\n\t): Promise<void> {","sourceCodeStart":1427,"sourceCodeEnd":1463,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/vibe/runtime.ts#L1427-L1463","documentation":"The per-turn job wrapper in #registerTurnJob catches any error thrown while running a vibe worker turn (spawn failure, persistence ToolError, provider error, abort), finalizes the turn via #finishTurn, records the failure in record.lastActivity, and rethrows it as a VibeTurnError prefixed with the worker id, cli, and turn index. It is a normalizing wrapper: the original error's message becomes the reason; only VibeTurnError instances pass through untouched. The wrapped error surfaces to the caller that awaited the async job (e.g. vibe send/follow-up APIs).","triggerScenarios":"Any failure inside a turn: runSubprocess/runSubagentFollowUpThrow throwing (bad agent config, missing CLI, spawn failure), the turn-started persistence ToolError (2728), provider/API errors, or cancellation — anything that is not already a VibeTurnError.","commonSituations":"Worker CLI binary not installed or agent name invalid; model auth expired mid-turn; parent session scope changed (see 2728); network failure during the subagent's LLM call; user aborted the job.","solutions":["Read the embedded reason after 'turn failed:' to identify the root cause (it is the original error message).","If the reason is the scope-change ToolError, restore the original parent session scope before sending further turns.","For spawn/config reasons, fix the agent definition/CLI/model credentials and start a new turn or new worker.","Inspect record.lastActivity (first line of 'turn failed: ...') in the vibe status listing to triage without catching the exception."],"exampleFix":"// before: generic handling loses context\ncatch (e) { log(e.message); }\n// after: unwrap the VibeTurnError and act on the reason\ntry {\n  await vibe.send(workerId, msg);\n} catch (e) {\n  if (e instanceof VibeTurnError && e.message.includes(\"changed parent scope\")) {\n    await session.switchTo(originalSessionFile);\n    await vibe.send(workerId, msg);\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"// validate inputs before dispatching a turn\nif (!record.agent) throw new Error(\"vibe agent definition missing\");\nif (record.state === \"dead\") throw new Error(`vibe worker ${record.id} is dead; spawn a new one`);","typeGuard":"function isVibeTurnError(e: unknown): e is VibeTurnError {\n  return e instanceof VibeTurnError;\n}","tryCatchPattern":"try {\n  await vibe.send(workerId, message);\n} catch (error) {\n  if (isVibeTurnError(error)) {\n    const match = error.message.match(/^\\[vibe:(\\S+) cli=(\\S+) turn=(\\d+)\\] turn failed: (.*)$/s);\n    if (match) {\n      const [, id, cli, turn, reason] = match;\n      logger.warn(\"vibe turn failed\", { id, cli, turn, reason });\n      // dispatch on `reason` (scope change, spawn failure, provider error)\n      return;\n    }\n  }\n  throw error;\n}","preventionTips":["Always parse the reason after 'turn failed:' — it is the original root-cause message.","Check record.lastActivity in vibe status output to triage failures without triggering turns.","Validate agent/CLI/model configuration before spawning workers to avoid turn-level spawn failures.","Handle VibeTurnError explicitly; it is the guaranteed wrapper for all in-turn failures."],"tags":["vibe","turn","error-wrapping","subagent"],"backgroundTag":"vibe-turn-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}