Error Handling
Error codes, retry strategies, and troubleshooting across SDK, server, and widget.
SDK error codes
When your handler throws or a tool call fails, the SDK returns structured errors:
| Code | Retryable | Meaning |
|---|---|---|
FUNCTION_NOT_FOUND | No | Tool name not registered |
HANDLER_ERROR | No | Exception in your handler |
NO_SEND_HANDLER | No | onSend not registered (BYO channel) |
SEND_FAILED | Yes | Delivery handler failed |
Handler exceptions are caught and returned as:
{
"ok": false,
"error": {
"code": "HANDLER_ERROR",
"message": "Order not found",
"retryable": false
}
}Server protocol errors
| Code | Meaning |
|---|---|
invalid_json | Malformed WebSocket frame |
invalid_message | Zod validation failure |
not_registered | Action before register frame |
set_state_failed | Lifecycle state update failed |
set_flow_progress_failed | Flow progress update failed |
Widget error codes
| Code | Auto-retry | Meaning |
|---|---|---|
origin_not_allowed | No | Page origin not in allowlist |
auth_required | No | Token required but missing |
identity_mismatch | No | Token doesn't match customerId |
| Handshake timeout | Yes (max 10) | No response within ~8s |
| Connection drop | Yes (exponential backoff) | Network blip or server restart |
The widget shows a user-friendly message for turn processing failures: "Something went wrong…" with a Retry button.
Safety gate errors
Before every tool call, the server evaluates safety gates in order:
Missing required args — server asks the user to provide them (recoil/suspend).
State tool gating — denies if the tool is blocked in the current lifecycle state.
Guard conditions — asks for more context if guard is not satisfied.
Safety policy — denies or requires confirmation (needs_approval) based on the tool's safety level.
Destructive actions blocked
Actions with safety: 'destructive' are blocked entirely in V1 and cannot be executed regardless of user intent.
LLM degradation
| Condition | Behavior |
|---|---|
| Missing API key | Degrades to mock LLM with warning |
AELIO_REQUIRE_LLM_KEY=1 | Hard-fail on boot |
| Primary provider failure | Falls back to configured fallback chain |
Retry strategies
SDK auto-reconnect
Exponential backoff, max 30s delay. Re-sends register frame on reconnect.
Widget auto-reconnect
Exponential backoff, max 10 attempts. Does not retry on origin_not_allowed.
Proactive message retries
Failed deliveries are retried with exponential backoff for up to 72 hours.
Troubleshooting checklist
| Symptom | Check |
|---|---|
| Widget shows "Connecting..." forever | serverUrl, server /health, channels.web.enabled |
| "Origin not allowed" | Add origin to channels.web.allowed_origins |
| SDK tools not invoked | AELIO_SDK_SECRET matches, SDK connected (/ready) |
| Mock LLM responses | Set OPENAI_API_KEY or other provider key |
| Write actions not executing | User must confirm — check widget confirmation UI |
| WhatsApp messages not received | Webhook URL, verify token, HMAC signature |
| History not loading | Requires server v0.1.2+ with matching widget build |
Logging
SDK tool events in the Shopping Testbed:
[aelio:tool.start] search_products { customerId: 'cus_...', args: { query: 'headphones' } }
[aelio:tool.ok] search_products (42ms)
[aelio:tool.error] checkout_cart { message: 'Cart is empty' }Server logs include turn processing, safety gate decisions, and LLM call records (available via /api/v1/telemetry/turn-calls).