Error handling
Failing on purpose, what the caller sees, and which errors are worth retrying.
Four kinds of error
| Kind | Example | What the caller gets | Retry? |
|---|---|---|---|
| Rejected input | a field fails the action's input schema | action_contract_validation_failed; your code did not run | no — fix the input |
| Your code decided no | throw new Error("Managers only") | the execution fails with action_failed and your message | no |
| Your code has a bug | a TypeError, or an output that fails output | action_failed or execution_output_invalid | no — fix the action |
| The platform | the result took longer than 110 s, a network drop | a 5xx code such as execution_result_timeout | yes, with the same request id |
Only the last kind is worth retrying automatically, and retrying with the same request id never runs your code twice.
Failing on purpose
Throw. The message is what the caller sees:
async execute({ input, runtime }) {
if (!runtime.auth.user) throw new Error("Sign in to place an order");
const { orders_order } = await runtime.query({
orders_order: { $: { where: { id: input.orderId } } },
});
if (orders_order[0]?.status !== "open") throw new Error("This order is already closed");
// ...
}Throw before your first runtime.tx. An error after a tx commits leaves that write in place.
What reaches the caller
An action call answers with its execution. A failed one carries status: "failed", the code action_failed, and your message, truncated to 1000 characters. There is no stack trace, in any environment.
Messages reach your users, so write them for people, and never put secrets, tokens or internal ids in them.
try {
await db.actions.orders.place({ orderId, total });
} catch (error) {
toast(error.message); // "This order is already closed"
}order_closed) so the client can branch without matching messages.Reads that fail
A query that fails — too slow, malformed — returns an error from useQuery instead of data. Hidden objects are not an error: they are absent. See Errors for the codes.
Workflows
When a workflow run gives up, its execution fails with workflow_failed. Steps that finished stay finished: design each step so the run can be retried or compensated.
Finding out what happened
Every call has an execution: GET /executions/{id} has its status, output and error. See the HTTP API.