Error Handling

Diminuendo employs a layered error handling strategy. Transport-level errors (malformed JSON, oversized messages, rate limiting) are caught at the WebSocket handler before any business logic executes. Domain-level errors (authentication failures, session not found, insufficient credits) are raised within the Effect TS runtime and mapped to structured error events. All errors are sanitized before reaching clients — stack traces are stripped, API keys are redacted, and messages are truncated to a safe length.

Error Event Format

All errors are delivered as a server event with type: "error":
{
  "type": "error",
  "code": "SessionNotFound",
  "message": "Session not found"
}
type
string
Always "error".
code
string
A machine-readable error code. Use this for programmatic error handling (switch statements, retry logic, UI rendering).
message
string
A human-readable description. Safe for display to end users — guaranteed to contain no stack traces, no API keys, and no internal implementation details.
Error events are not session-scoped — they do not carry sessionId, seq, or ts fields. They are sent directly to the connection that triggered the error, never broadcast to session subscribers.

Error Codes

Transport-Level Errors

These errors are raised at the WebSocket handler layer before the message reaches the business logic:
CodeConditionDescription
INVALID_JSONMessage is not valid JSONThe raw WebSocket frame could not be parsed as JSON. Typically caused by incomplete messages, binary data sent to a text frame, or encoding errors.
INVALID_MESSAGEJSON does not match any schemaThe JSON was parsed successfully but does not match any of the 21 client message schemas. Common causes: unknown type field, missing required fields, wrong field types.
MESSAGE_TOO_LARGERaw message exceeds 1 MBThe message was rejected before parsing. The gateway enforces a 1 MB size limit on inbound WebSocket frames.
RATE_LIMITEDPer-connection rate limit exceededMore than 60 messages in a 10-second sliding window. The client must slow down.
AUTH_RATE_LIMITEDAuthentication attempts exceededToo many authenticate messages from the same IP address in a short period. Includes a retryAfterMs hint in the message text.

Authentication & Authorization Errors

CodeConditionDescription
NOT_AUTHENTICATEDMessage sent before authenticationThe client attempted to send a message (other than authenticate) before completing authentication.
AUTH_FAILEDToken validation failedThe JWT or API key was invalid, expired, or could not be verified against the Auth0 JWKS endpoint.
UnauthenticatedAuthentication requiredA gateway-internal error indicating the operation requires authentication.
UnauthorizedInsufficient permissionsThe authenticated user does not have the required RBAC permission for this operation (e.g., a member attempting manage_members with set_role action).

Domain Errors

CodeConditionDescription
SessionNotFoundSession does not existThe referenced sessionId does not exist in the tenant’s registry, or the session has been deleted.
SessionAlreadyExistsDuplicate session creationAttempted to create a session with an ID that already exists (rare — UUIDs are generated server-side).
INSUFFICIENT_CREDITSBilling check failedThe tenant does not have enough credits to start a turn. No agent interaction occurs.
PodiumConnectionErrorAgent connection failedFailed to establish or maintain a WebSocket connection to the Podium agent orchestrator.
PodiumTimeoutAgent operation timed outA Podium operation (instance creation, message send) exceeded its timeout threshold.
EnsembleErrorLLM inference failureAn error from the Ensemble inference service (model unavailable, provider error, token limit exceeded).
SandboxNotConfiguredNo sandbox availableThe operation requires a sandbox environment, but none is configured for this session.
DbErrorDatabase operation failedA SQLite operation failed (disk full, corruption, concurrent write conflict).
ProtocolVersionMismatchVersion mismatchThe client and gateway disagree on the protocol version.
INTERNAL_ERRORUnclassified errorA catch-all for unexpected failures. The message is sanitized before delivery.

Turn-Specific Errors

Turn errors are delivered via the turn_error event type (not the generic error event) because they are session-scoped and carry seq/ts fields:
CodeConditionDescription
AGENT_ERRORAgent reported an errorThe Podium agent encountered an error during execution (e.g., LLM API failure, tool crash).
AGENT_DISCONNECTEDWebSocket dropped mid-turnThe gateway lost its WebSocket connection to the Podium agent while a turn was in progress. The session transitions to error state.
turn_error events are broadcast to all session subscribers, not just the client that initiated the turn. This ensures all connected clients see the error state and can update their UI accordingly. The session transitions to error state on turn errors — clients should offer a retry action.

Member Management Errors

CodeConditionDescription
INVALID_MEMBER_UPDATEMissing or invalid fieldsThe set_role action was missing userId or role, or the role is not a valid value.
LAST_OWNER_PROTECTEDCannot demote last ownerThe operation would leave the tenant with no owners. Promote another member to owner first.

Error Sanitization

All error messages pass through a sanitization pipeline before reaching clients. This is a critical security measure — internal errors may contain stack traces, file paths, API keys, or other sensitive information that must never be exposed to end users. The sanitization process applies three transformations in order:
1

Strip Stack Traces

Any line matching the pattern of a stack trace (at Function.name (/path/to/file.ts:42:10)) is removed. This prevents leaking internal file paths and call stacks.
2

Redact Secrets

The following patterns are replaced with [REDACTED]:
  • Anthropic API keys: sk-ant-*
  • Generic secret keys: sk-*
  • GitHub personal access tokens: ghp_*
  • Bearer tokens: Bearer *
  • URL token parameters: token=*
3

Truncate

Messages exceeding 500 characters are truncated with an ellipsis (...). This prevents pathologically long error messages (e.g., from serialized stack traces or large JSON payloads) from bloating WebSocket frames.
The sanitization is applied by the sanitizeErrorMessage function in the gateway’s security module. It processes errors from all sources — Effect TaggedErrors, JavaScript Errors, plain strings, and arbitrary objects (via JSON.stringify). The pipeline is deterministic: the same error always produces the same sanitized message.

Gateway Typed Errors

Internally, the gateway uses Effect’s TaggedError pattern to define typed, structured error classes. These provide type-safe error handling within the Effect runtime and are mapped to error codes when sent to clients.
Error ClassTagFieldsWire Code
Unauthenticated"Unauthenticated"reason: stringUnauthenticated
Unauthorized"Unauthorized"tenantId: string, resource: stringUnauthorized
SessionNotFound"SessionNotFound"sessionId: stringSessionNotFound
SessionAlreadyExists"SessionAlreadyExists"sessionId: stringSessionAlreadyExists
InsufficientCredits"InsufficientCredits"tenantId: string, required: number, available: numberINSUFFICIENT_CREDITS
PaymentFailed"PaymentFailed"reason: string, stripeError?: stringPaymentFailed
PodiumConnectionError"PodiumConnectionError"message: string, cause?: unknownPodiumConnectionError
PodiumTimeout"PodiumTimeout"operation: string, timeoutMs: numberPodiumTimeout
EnsembleError"EnsembleError"message: string, statusCode?: numberEnsembleError
SandboxNotConfigured"SandboxNotConfigured"message: stringSandboxNotConfigured
DbError"DbError"message: string, cause?: unknownDbError
InvalidMessage"InvalidMessage"reason: string, raw?: stringInvalidMessage
ProtocolVersionMismatch"ProtocolVersionMismatch"expected: number, received: numberProtocolVersionMismatch
The message router’s catch-all handler maps the _tag field of each TaggedError to the wire code, and provides safe, predefined messages for known error types:
const safeMessages: Record<string, string> = {
  Unauthenticated: "Authentication required",
  Unauthorized: "Insufficient permissions",
  SessionNotFound: "Session not found",
  PodiumConnectionError: "Failed to connect to agent",
  DbError: "Database operation failed",
  InsufficientCredits: "Insufficient credits",
}
For unrecognized error tags, the original error message is passed through sanitizeErrorMessage before being sent to the client.

Recovery Strategies

Different error codes call for different recovery approaches. The table below provides guidance for client implementations:
Error CodeStrategyDetails
AUTH_FAILEDRe-authenticateThe token may be expired. Obtain a fresh JWT from your identity provider and send a new authenticate message.
AUTH_RATE_LIMITEDExponential backoffParse the retry delay from the error message. Wait at least that long before attempting authentication again.
NOT_AUTHENTICATEDRe-authenticateThe connection may have been reset. Send an authenticate message before retrying the original operation.
UnauthorizedSurface to userThe user lacks the required permission. Display an appropriate message and do not retry.
SessionNotFoundRefresh session listThe session may have been deleted by another client or a concurrent operation. Call list_sessions to refresh the UI.
INSUFFICIENT_CREDITSSurface to userThe tenant has exhausted its credits. Direct the user to the billing interface to add credits before retrying.
RATE_LIMITEDBackoff and retryReduce message frequency. Implement a client-side rate limiter to stay within 60 messages per 10 seconds.
PodiumConnectionErrorRetry with backoffThe agent backend may be temporarily unavailable. Wait 2-5 seconds and retry the operation. If persistent, the agent infrastructure may be down.
AGENT_DISCONNECTEDOffer retryThe agent’s WebSocket connection dropped mid-turn. The session is in error state. The user can retry the turn with run_turn.
AGENT_ERROROffer retryThe agent encountered an internal error. The user can retry the turn. If the error persists, it may indicate a problem with the agent’s configuration or the underlying LLM.
INTERNAL_ERRORRetry once, then surfaceAn unexpected error. Retry the operation once. If it fails again, surface the error to the user and consider reconnecting.
MESSAGE_TOO_LARGEReduce payloadThe message exceeds 1 MB. Reduce the size of the content (e.g., truncate very long prompts).
INVALID_JSONFix client bugThe client is sending malformed JSON. This is always a client-side bug.
INVALID_MESSAGEFix client bugThe message structure is wrong. Check field names, types, and required fields against this documentation.
The SDKs handle several of these recovery strategies automatically. The TypeScript SDK’s autoReconnect option re-establishes the connection and re-authenticates on disconnect. The Python SDK’s auto_reconnect does the same. For session-level recovery (re-joining with afterSeq), the client application must implement its own logic — the SDKs provide the primitives but do not make assumptions about which sessions to rejoin.

Error Handling in SDKs

Each SDK surfaces errors through its native error handling paradigm:
const client = new DiminuendoClient({ url: "ws://localhost:8080/ws" })

// Connection errors throw during connect()
try {
  await client.connect()
} catch (err) {
  // "AUTH_FAILED: Authentication failed"
  // "Connection timeout while waiting for authentication"
}

// Request-response methods reject their promises
try {
  await client.joinSession("nonexistent-id")
} catch (err) {
  // "SessionNotFound: Session not found"
}

// Listen for error events on the connection
client.on("error", (event) => {
  console.error(`[${event.code}] ${event.message}`)
})

// Listen for turn errors on the session
client.on("turn_error", (event) => {
  console.error(`Turn failed: [${event.code}] ${event.message}`)
})