Skip to main content
Every backend error returns { error, message, statusCode, nextActions } - nextActions tells you how to fix the call. The CLI prints it automatically.

Common errors

REALTIME_UNAUTHORIZED on subscribe. No declared channel pattern covers that channel. Fix: npx @resultdev/cli realtime add-channel "chat:%" - the pattern alone unlocks it. “permission denied” on storage upload or list(). The caller is anonymous. Uploads and listing require a signed-in user - call backend.auth.signUp(...) or signInWithPassword(...) first. Insert silently fails or 400s. insert() takes an array: .insert([{ title: "Hi" }]), not .insert({ title: "Hi" }). Signed-in queries return no rows on an RLS table (and writes 403 with code 42501). Most often the table has no policy: RLS with zero policies denies every read and write, and reads fail silently as []. Recreate the table through result db create-table with a user_id:uuid column (the owner policy is generated for you) or add a policy with result db migrate. If a policy exists, the rows were created without a user_id matching the session (e.g. seeded with the admin key) - backfill it or recreate the rows as the user. functions.invoke() throws “Failed to fetch” in the browser. @resultdev/sdk older than 0.3.0 routed browser calls through a retired host. Upgrade to the latest SDK. Realtime publishes succeed but subscribers receive nothing. Same cause: SDK older than 0.3.0 tagged incoming messages with a prefixed channel name, so channel filters never matched. Upgrade, and confirm the channel pattern is declared. AI request with an image or audio fails (AI_UPSTREAM_UNAVAILABLE). Use google/gemini-2.5-flash - the verified model for image and audio input. Audio must be wav/mp3/aiff/aac/ogg/flac/m4a; browser MediaRecorder’s webm is rejected. Sign-in works, then a reload shows the user signed out. Almost always no getCurrentUser() call when the app mounts, so nothing redeems the stored session. That call is what restores it, and onAuthStateChange() does not stand in for it: it reports changes only, never the session that already exists. If you do call it, upgrade to SDK 0.7.0 or newer - before that the session rode a cookie Safari, incognito windows and framed previews refuse to send. The login screen flashes on every reload for a signed-in user. The UI has two states where it needs three. The restore is async, so render nothing auth-dependent until getCurrentUser() resolves. No auth token in localStorage. By design. The access token is in memory and the refresh token is a first-party cookie - check sign-in state with backend.auth.getCurrentUser(), never by reading browser storage. Migration rejected. Names allow lowercase letters, numbers and hyphens only (the CLI normalizes this for you), and the SQL must not contain BEGIN/COMMIT - it already runs in one transaction. 401 from a hand-rolled HTTP call. Don’t hand-roll - use the SDK or CLI. (If you must know: end-user calls send Authorization: Bearer <publishable key>, not an x-api-key header.)

Debugging workflow

  1. npx @resultdev/cli status - does the table/bucket/function exist at all?
  2. npx @resultdev/cli logs - pick a source, read recent entries.
  3. npx @resultdev/cli auth users - did the signup actually land?
  4. Check error.nextActions on the failing SDK call.

Facts that prevent bugs

  • Every table has id, created_at, updated_at automatically.
  • Secrets are effectively permanent - deleting is unreliable; rotate by setting a new key instead.
  • Email from is a sender name, not an address.
  • AI + email work with the publishable key - gate spend behind a function.