can1357/oh-my-pi · error · VibeTurnError
[vibe:${record.id} cli=${record.cli} turn=${turnIndex}] turn
Error message
[vibe:${record.id} cli=${record.cli} turn=${turnIndex}] turn failed: ${reason} What it means
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).
Source
Thrown at packages/coding-agent/src/vibe/runtime.ts:1445
? await runSubprocess(await this.#buildSpawnOptions(session, record, message, signal, onProgress))
: await runSubagentFollowUpTurn({
id: record.id,
agent: record.agent,
message,
description: `vibe ${record.cli} session`,
signal,
onProgress,
eventBus: session.eventBus,
subagentEventBus: session.subagentEventBus,
artifactsDir: session.getSessionFile()?.slice(0, -6),
});
return await this.#settleTurn(session, manager, record, turn, ownJobId, turnIndex, result);
} catch (error) {
if (error instanceof VibeTurnError) throw error;
await this.#finishTurn(session, manager, record, ownJobId);
const reason = error instanceof Error ? error.message : String(error);
record.lastActivity = firstLine(`turn failed: ${reason}`);
throw new VibeTurnError(
`[vibe:${record.id} cli=${record.cli} turn=${turnIndex}] turn failed: ${reason}`,
);
}
},
{ id: `${record.id}-t${turnIndex}`, agentId: record.id, ownerId: record.ownerId },
);
turn.jobId = jobId;
record.turn = turn;
return jobId;
}
/** Post-turn bookkeeping shared by success and failure paths: clear the in-flight turn, flush the queue. */
async #finishTurn(
session: ToolSession,
manager: AsyncJobManager,
record: VibeRecord,
settledJobId: string,
): Promise<void> {View on GitHub (pinned to 9690622007)
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.
Example fix
// before: generic handling loses context
catch (e) { log(e.message); }
// after: unwrap the VibeTurnError and act on the reason
try {
await vibe.send(workerId, msg);
} catch (e) {
if (e instanceof VibeTurnError && e.message.includes("changed parent scope")) {
await session.switchTo(originalSessionFile);
await vibe.send(workerId, msg);
} else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// validate inputs before dispatching a turn
if (!record.agent) throw new Error("vibe agent definition missing");
if (record.state === "dead") throw new Error(`vibe worker ${record.id} is dead; spawn a new one`); Type guard
function isVibeTurnError(e: unknown): e is VibeTurnError {
return e instanceof VibeTurnError;
} Try / catch
try {
await vibe.send(workerId, message);
} catch (error) {
if (isVibeTurnError(error)) {
const match = error.message.match(/^\[vibe:(\S+) cli=(\S+) turn=(\d+)\] turn failed: (.*)$/s);
if (match) {
const [, id, cli, turn, reason] = match;
logger.warn("vibe turn failed", { id, cli, turn, reason });
// dispatch on `reason` (scope change, spawn failure, provider error)
return;
}
}
throw error;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ${message}${hint}
- Subagent execution failed: ${error instanceof Error ? error.
- Vibe session "${record.id}" changed parent scope before its
- transparent (brush_core::Error)
- lines.join("\n")
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e173cbc655903758.
Report an issue: GitHub.