Troubleshooting & FAQ
Symptoms first, then the answers to the questions that come up most. Every row links to the page that explains the setting behind it. For the complete list of error codes see Error Handling.
The server does not start
createAgentServer and the register* methods validate their input and throw at startup, so a bad configuration fails before the first request:
| Message (shortened) | Cause and fix |
|---|---|
auth.enabled needs auth.provider ... | Token auth is switched on with nobody to check tokens. Add a provider, or set enabled: false and use resolveUserId / resolveContext (Authentication). |
limits.maxFileSize (...) is larger than the ... bytes this storage provider can hold (also files.maxFileSize, limits.maxStateBytes) | A limit above what the provider holds. Lower it, or use a provider that holds more (Storage Providers). |
The server has a budget but no pricing / Agent "x" has a budget but no pricing | A budget needs a pricing function, the server's or the agent's (Usage & Budgets). |
titleGeneration needs baseUrl and model ..., or a generate function | The title settings are incomplete (Chat experience). |
A host agent needs info.id / Host agent "x" needs an invoke function | The host-backed definition cannot work (Host-backed agents). |
JWTAuthProvider needs a secret ... | createJWTAuthProvider without a key, or with a key of the wrong kind for the algorithm. |
Storage provider is not connected (a 500) | await storage.connect() before the first request. |
A request is refused
| Answer | What it means | Fix |
|---|---|---|
401 identity_required | A list request that names no user | Resolve a user for every request, or set allowAnonymousList: true on a single-user server (Request Context) |
401 AUTHENTICATION_ERROR | No or invalid token, or resolveContext returned null | Check the header and the token (Authentication) |
403 FORBIDDEN | The conversation, task or file belongs to someone else | Conversations belong to the user id the server resolved; keep it stable between requests |
404 NOT_FOUND | Unknown, or not yours (a file of someone else answers like a missing one), or an agent not served to this request | |
402 budget_reached | The conversation's budget is used up | Raise it and continue (Usage & Budgets) |
409 conversation_busy | A turn is running in that conversation | Wait, follow it, or POST .../cancel (Runs) |
409 action_pending | A tool approval or question is open | Answer it, or cancel it (Approvals) |
410 action_expired | The request lapsed | Reload the conversation; hitl.onTimeout has been applied |
413 PAYLOAD_TOO_LARGE | The body is over the adapter's limit | limits.maxBodySize, uploadBodyLimit (Fastify) (Files) |
413 STORAGE_LIMIT_EXCEEDED | The provider cannot hold the file or the state | Storage Providers |
415 unsupported_media_type / unsupported_file_type | A form upload / a type the agent does not accept | Send the bytes as the body, check allowedMimeTypes |
429 rate_limited | Your rateLimit hook refused | Wait Retry-After (Embedding & limits) |
501 export_format_unsupported | format=pdf without export.renderPdf | Use format=html&print=1 |
A turn does not behave
| Symptom | Read |
|---|---|
| The answer is lost when the tab closes, or a Stop button does nothing | Runs, Cancel & Reattach |
| The tool runs without asking, or an approval cannot be answered | Approvals & Questions |
No costUsd, or the budget does not stop a turn | Usage & Budgets |
| The agent does not read the file I attached | Files |
| A custom handler does not stream | Custom Agents |
| My backend agent behaves differently from an SDK agent | Host-backed agents |
The browser blocks the chat, or an embedded chat gets 401 | Embedding & limits |
FAQ
What does stopReason mean?
It is why the last turn stopped, on the assistant message (metadata.stopReason), on the response and stream.done, in session.status_idle events and in conversation.runtime.stopReason:
stopReason | Meaning |
|---|---|
end_turn | The agent finished its answer |
requires_action | The turn paused for an approval, a question or a client tool |
cancelled | Someone pressed Stop (POST .../cancel), or an unanswered request was dropped under hitl.onTimeout: "cancel" |
max_turns, limit | The agent stopped on its own limits: an agent-sdk run that breached a limit (limit), or a handler or host agent reporting max_turns / limit itself |
budget_reached | The conversation's budget ended the turn |
error | The turn failed; runtime.error says why (run_interrupted is a run whose server died) |
Do I need a database?
For development, createInMemoryProvider() needs nothing and loses everything on restart. SQLite (better-sqlite3) is one process and one file. PostgreSQL or MongoDB are for production and for more than one server instance (Storage Providers).
Can I run several instances?
Yes, on PostgreSQL or MongoDB shared by all of them: one turn per conversation holds across instances through leases, cancel reaches a run on another instance, and the event stream can be served by any instance. Keep the clocks within a few seconds (Architecture).
Does an approval survive a restart?
With every built-in provider except memory, yes: the paused state is stored with the conversation, and a new server on the same database resolves a pause the old one started (Approvals & Questions).
Where is tenant isolation?
The server separates users, not tenants. Run a server per tenant, put the tenant in the user id, or scope in your own storage provider (Request Context).
How long is data kept?
Files: files.retentionDays (30; 0 for ever). Events: runs.eventRetentionDays (30; 0 for ever). Conversations and messages are kept until deleted. Approval requests lapse after hitl.timeoutMinutes (1440; 0 never).
How do I see what a conversation did?
Read its event log: GET /conversations/:id/events (or follow it with .../events/stream). It records the person's message, every tool call and result, usage, approvals and how each turn ended (Runs).
Is the task callback signed or retried?
No. It is one POST with no credential and no retry; a failure is only logged. Read the task from the API when it arrives (Task System).
Can I restrict who answers an approval?
Yes: hitl.authorizeResolve(ctx, { conversation, action }) decides for everyone, the owner included (Approvals & Questions).
Can a conversation cost more than its budget?
By at most one model call for an agent-sdk agent that has a costEstimator; by the turn that crosses the limit for a handler or host agent, which cannot be stopped in the middle (Usage & Budgets).
Where do I report a problem?
On GitHub Issues. Include the package version, the storage provider, the adapter, and the error.code and runId of the failing request.

