Skip to content

Errors

Every error a call can fail with, what it means, and what to do about it. This page is for anyone who has read Write data and is handling the failure path.

New here? Start with Getting started.

Every error is a tagged value and DbError is the union of the eight request errors. Most come from the Ramose server — the one Cloudflare Worker that serves all your databases; Ramose’s code calls it the peer. .oneOrFail() can also fail with NotOne, and a parameterized query can fail with ParamError — neither is a DbError. Match by name with Effect.catchTags.

const rows = yield* db.q(query).pipe(
Effect.catchTags({
QueryBudgetExceeded: () => Effect.succeed([]),
Unavailable: () => Effect.succeed([]), // the writer is restarting; try later
}),
);
tagmeanstypical cause
TxRejectedthe write was refused by the writer — the one thing per database that commits writes (glossary); no version number usedschema violation, unique-key conflict, a rule that failed at commit, schema not installed
Unavailablethe server cannot serve right now (503)the writer restarting after a failed storage write; carries retryAfterMs
InvalidRequestthe request is malformed (400)bad database name, unknown field, bad query or shape
DatabaseNotFoundthe route does not exist (404)a wrong server URL or path — a database name always exists
Unauthorizedyou may not do this (401/403)missing or expired sign-in token (glossary), a token for another database, a rule that denies; carries code: "policy" and the field name, never a value
QueryBudgetExceededthe query went over the server’s per-query memory limit (glossary) (413)too wide a query; carries clause, cells, limit
InternalErrorthe server failed (5xx)a bug or storage fault; logged on the server
NetworkErrorthe request never completedfetch failure, dropped connection, a mint that threw
NotOne.oneOrFail() did not see exactly one rowzero matches, or two (the server is asked for two so a second row is seen); found is 0 or 2
ParamErrorthe query’s params were bound wronga required param missing or undefined, an unknown key, or a deferred check (flagged RegExp, a non-entity for is)

Every error carries a message; errorMessage(e) from ramose/react is e.message ?? e._tag ?? String(e).

examples/reef/src/app/screens/BoardScreen.tsx:243-245
const { run } = useTransact({
onError: (error) => toast("error", errorMessage(error)),
});

useTransact hands error and onError the failure itself, so errorMessage works on it directly. The read hooks — useLive, useQuery, usePull — report an Effect Cause instead, so unwrap it first: errorMessage(Cause.squash(error)), with Cause from effect/Cause.

tagwhat to showwhat to do
Unauthorized (policy)the server’s message as a toast — in Reef a viewer who drags a card sees retract denied on :issue/statusnothing to retry; the button was only a hint
Unauthorized (token)“sign in again”refresh the sign-in; a live query stops on it
TxRejected“someone changed this; try again”re-read, then retry with fresh data
Unavailable / NetworkError“offline — retrying” or “try again”already tried 6 times for you; if it still surfaces, the whole write may be retried — nothing landed
InvalidRequest / DatabaseNotFounda generic errorfix the app: name, field, URL
QueryBudgetExceededa generic errornarrow the query; not retried
InternalError“something went wrong”try later
NotOnea generic errorthe filter matched zero or several records; tighten it
ParamErrora generic errorfix the binding: every required hole, no extra keys
  • Unavailable and NetworkError are retried for you — 6 attempts on a jittered ladder from about 150 ms doubling to 2 s — on HTTPS and on the WebSocket alike. Nothing else is retried.
  • A live query keeps going. Beyond that ladder, dropped sockets and 5xx are retried with backoff (250 ms to 5 s). It stops only on InvalidRequest, DatabaseNotFound, Unauthorized, QueryBudgetExceeded, NotOne or ParamError.
  • A write is not retried past the ladder. An Unavailable after a writer restart means nothing from that write is stored — retry the whole write.
  • TxRejected never retries. Retrying the same write against the same data rejects again.
  • Setup mistakes are not errors. A missing service binding or malformed URL throws at start-up instead of failing requests.
statuserror
400InvalidRequest
401 / 403Unauthorized ({ error, code: "policy", attr } for a rule)
404DatabaseNotFound (unknown route)
409TxRejected ({ error, tag, code })
413QueryBudgetExceeded
503 (+ retry-after)Unavailable
other 5xxInternalError
no responseNetworkError

A Cloudflare error page (HTML 404, 1xxx) is treated as Unavailable, so a deploy in progress retries instead of failing.