Session errors & error events
VoiceThere records structured session errors in the dashboard (last 20 per project) and emits matching session_error events on the voice-control data channel. Browser and Node clients use onSessionError from @voicethere/client/browser; agent bundles can implement errorHook in @voicethere/agent.
Error event envelope (data channel)
New payloads use type: "session_error". Legacy agent_error JSON is still accepted by the client and mapped to AGENT_CHILD_CRASHED.
{
"type": "session_error",
"code": "AGENT_CHILD_CRASHED",
"message": "Sorry, something went wrong.",
"session_id": "<orchestrator-session-id>",
"project_id": "<uuid>",
"build_id": "<uuid>",
"stack": "Error: …\n at …",
"recoverable": false,
"customer_context": { "userId": "u_123" },
"occurred_at": "2026-06-19T12:00:00.000Z"
}Error code catalog
All codes are defined in SESSION_ERROR_CODES (platform, runner, client). Sources stored in the database: agent, runner, provisioning.
Runner / agent (remote — on voice-control)
| Code | When |
|---|---|
| AGENT_HANDLER_FAILED | Agent handler threw; errorHook ran; runner ending session |
| AGENT_CHILD_CRASHED | Child process exit or unhandled bundle load failure |
| RUNNER_INTERNAL | Runner-side failure (e.g. crash TTS could not play) |
| SESSION_END_FAILED | Session marked failed on teardown |
| IDLE_TIMEOUT_CALLBACK_FAILED | Agent onIdleTimeout hook threw |
| IDLE_TIMEOUT_CALLBACK_TIMED_OUT | onIdleTimeout exceeded 30s grace |
| SESSION_IDLE_TIMEOUT | Informational — peer idle limit reached (often paired with session_close) |
Client-only (local — never on data channel)
Emitted by @voicethere/client to onSessionError before or without a live WebRTC leg.
| Code | When |
|---|---|
| PROVISIONING_FAILED | Session start job failed or HTTP error |
| PROVISIONING_TIMEOUT | Async provisioning poll exceeded timeout |
| WEBRTC_CONNECTION_FAILED | Peer connection state became failed |
| WEBRTC_CONNECTION_CLOSED | Unexpected close before graceful disconnect |
| WEBRTC_CONNECT_TIMEOUT | waitForConnected timed out |
Full enum (12 codes): AGENT_HANDLER_FAILED, AGENT_CHILD_CRASHED, RUNNER_INTERNAL, SESSION_END_FAILED, IDLE_TIMEOUT_CALLBACK_FAILED, IDLE_TIMEOUT_CALLBACK_TIMED_OUT, SESSION_IDLE_TIMEOUT, PROVISIONING_FAILED, PROVISIONING_TIMEOUT, WEBRTC_CONNECTION_FAILED, WEBRTC_CONNECTION_CLOSED, WEBRTC_CONNECT_TIMEOUT.
@voicethere/agent — errorHook
Optional hook in defineAgent runs in the sandboxed child when handler code throws, before the runner plays crash TTS and ends the session. Must not throw.
import { defineAgent, agentLog } from "@voicethere/agent";
defineAgent({
async errorHook({ sessionId, error, customerContext }) {
agentLog("error", `session ${sessionId}: ${error.message}`);
// Optional: sendToClient(sessionId, { type: "alert", message: error.message });
},
onUserSpeechFinal({ sessionId, text }) {
if (text.includes("crash")) throw new Error("demo failure");
},
});Pass opaque customerContext from the browser on session start; the client sends session_hello on voice-control open. The runner injects it as AGENT_CUSTOMER_CONTEXT JSON in the agent child env and echoes it on DC session_error payloads.
@voicethere/client — onSessionError
import { startSession, connectBrowserSession } from "@voicethere/client/browser";
const provision = await startSession({
apiBase: sessionServiceBase,
projectId,
headers: { Authorization: `Bearer ${apiKey}` },
onSessionError: (event) => {
if (event.recoverable) showRetryToast(event.message);
else showFatalError(event.code, event.message);
},
});
if (provision.ok) {
await connectBrowserSession({
mode: "voice",
credentials: provision.credentials,
customerContext: { tier: "pro" },
onSessionError: (event) => analytics.track("session_error", event),
});
}Configure crash TTS message
- Dashboard: project overview → Session settings → Error TTS message. Applied on next deploy as
AGENT_CRASH_TTS_MESSAGE. - CLI:
voicethere projects session-settings set error_message "Sorry, try again."
Control plane API
List and record errors via the platform API (runner/session-service use service credentials). See Control plane API for routes and OpenAPI.
GET /projects/:projectId/session-errors— last 20 errorsGET /projects/:projectId/sessions/:sessionId/errors— errors for one session- Runner pods enqueue to Redis queue
session-errors— session-service worker persists rows (no HTTP from runner) GET/PATCH /projects/:projectId/session-settings— idle timeout + error_message
CLI
voicethere projects errors list voicethere projects errors list --session <orchestrator-session-id> voicethere projects session-settings list
Migration: agent_error → session_error
Older runners sent { "type": "agent_error", "message": "…" }. Current runners send the full session_error envelope above. Update client code to use onSessionError instead of parsing onControlMessage for agent_error.