Function errors

When a function throws, Substrio records a typed error with parsed stack frames. You can read it by request, function, or revision.

Docs menu

Send the key as Authorization: Bearer $SUBSTRIO_KEY and keep it out of output. Select the app by its host or with X-Substrio-Tenant: <app>. Read errors with function.errors (MCP tool substrio_function_errors, GET /v1/_function_errors). A failed function.invoke or function.execute_inline (POST /v1/_exec) also returns structured error details.

The record

Every throw is recorded once in the app's function error log, where it occurs. The stack is parsed into frames when the entry is recorded.

{
  "id": "0000019a1b2c3d4e00000001:acme/thrower",
  "function": "acme/thrower",
  "revision": 3,
  "request_id": "req_41",
  "timestamp": 1760000000000,
  "name": "Error",
  "message": "deep boom",
  "frames": [
    {"function": "inner", "file": "thrower.js", "line": 1, "column": 31},
    {"function": "outer", "file": "thrower.js", "line": 2, "column": 25}
  ]
}
  • id: <timestamp as 16 hex digits><sequence as 8 hex digits>:<function id>. It is also the cursor: pass it as cursor to read the entries older than it.
  • function: the function id, <app>/<name>. revision: the function's newest deploy counter when it threw (version.log with the function path reports it per commit).
  • request_id: the call. The same value tags that call's log lines (function.logs filters by it).
  • timestamp: Unix milliseconds (UTC). name and message are the thrown error's. frames are innermost first; a stack line the grammar does not fit is kept whole as a frame whose function is the line and line is 0, so no line is dropped.
  • Entries are never trimmed: there is no retention cap.

Reading them: function.errors

curl -s "$SUBSTRIO_URL/v1/_function_errors?function=thrower&limit=2" \
  -H "Authorization: Bearer $SUBSTRIO_KEY"

Query options (MCP: the same names as tool arguments):

Option Meaning
function A function id (acme/thrower) or its bare name. Absent: every function.
request_id Only the entries of this call.
revision Only the entries recorded while this revision (the deploy counter of the function, which version.log reports per commit) was the function's newest: the errors of one deploy. A non-number is a 400.
since, until Unix seconds (UTC), inclusive: until takes its whole second.
limit Page size, a positive integer. Absent: every entry.
cursor A next_cursor this operation answered. Anything else is a 400.

Response:

{"errors": [ {entry}, ... ], "count": 2, "next_cursor": "0000019a1b2c3d4e00000001:acme/thrower"}

Entries come newest first. next_cursor is null on the last page. An invalid cursor, a zero limit, or since/until outside the range representable in milliseconds returns INVALID_ARGUMENT (400), with the option named in detail.

On the failed call itself

A failed function.invoke returns the standard error envelope (error, code CF_EXECUTION_FAILED, status 500, detail the message) plus one more member, function_error. It contains the entry's id, revision, request id, name, message, and frames.

{"error": "Cloud function execution error", "code": "CF_EXECUTION_FAILED",
 "status": 500, "retryable": false, "next_action": "reduce_request_or_increase_limit",
 "detail": "deep boom",
 "function_error": {
   "id": "0000019a1b2c3d4e00000001:acme/thrower", "revision": 3,
   "request_id": "req_41", "name": "Error", "message": "deep boom",
   "frames": [ ... ]}}

function_error.id is the stored entry's id. Pass it as cursor to function.errors to read older entries, or find the entry with request_id.

function.execute_inline records the same entry under the function id <app>/_exec, with a fresh request_id, and returns it as function_error beside error (status 500). Rejections that settle a call after a fetch or timer carry it too.

A failed scheduled or manual run names its entry in the schedule's history as error_id (schedules.md).

Refusals the runtime throws

A refused host call (db, chain, KV, ledger, files, cmd, queue, or fetch) throws an Error whose name is its code from the error table. A script can branch on e.name. The realm's TypeError is reserved for an argument of a type the call does not accept, and instanceof TypeError holds. If a wait reaches the call's deadline, the error becomes SubstrioTimeoutError, and the call returns 504 CF_WALL_TIMEOUT. Web APIs, including fetch argument checks, crypto, streams, and formData, throw errors according to their standards. See api/errors.md for the HTTP codes.

e.name Value Retryable next_action When
TypeError no an argument of a type the call does not take
INVALID_ARGUMENT 0x0102 no correct_request a value out of range, an unknown or empty name, an unknown $operator, a text the call refuses (a kv key with a character above U+00FF, a command with an unclosed quote or a NUL byte, a file path with an empty or .. segment)
UNSUPPORTED_OPERATION 0xF009 no choose_supported_operation a form the call does not run: a kv or graph write or a substrio service inside db.transaction, a transaction callback that returns a promise, a write after a join or a group, a regex flag other than i, a paged find with skip
TYPE_MISMATCH 0x8001 no correct_request stored data of another type than the call takes: $inc on a field that holds no number, db.kv on a name a collection's tree holds, an array for a unique member
KEY_NOT_FOUND 0x4000 no refresh_resource a live row or db.<name>[id] = doc whose document is no longer stored
KEY_TOO_LARGE 0x4001 no inspect_detail a kv key or a file path past 65,535 bytes
VALUE_TOO_LARGE 0x4002 no inspect_detail a kv value past the native value bound, or a document string or binary past 2,147,483,646 bytes
RECORD_TOO_LARGE 0x4013 no inspect_detail a document that would be past 4,294,967,295 bytes once stored
TOO_MANY_ELEMENTS 0x4014 no correct_request an array that would hold more than 4,294,967,295 elements
FIELD_NAME_TOO_LARGE 0x8008 no correct_request a field name past 65,535 bytes
TOO_DEEP 0x8009 no correct_request a value nesting more than 1024 objects and arrays, or an update path of more than 1024 steps
COLLECTION_NOT_FOUND 0xC004 no refresh_resource a collection that was dropped or cannot be opened
NO_CALL_CONTEXT 0xC036 no correct_request a db, collection, kv, graph, ledger or files handle used after the call that made it
LEDGER_UNAVAILABLE 0xC037 no inspect_detail db.ledger's store could not be opened
FETCH_FAILED 0xF00E no inspect_detail fetch() rejects: the upstream could not be reached or broke off (the message is the client's)
INVALID_QUERY_SYNTAX 0xB005 no correct_request a regex pattern that does not compile
MISSING_INDEX 0x4005 no inspect_detail a search that needs a search configuration the collection does not have
INVALID_BOUNDS 0x7002 no inspect_detail withinBox with no corners or corners of two lengths, withinSphere with an empty center
INVALID_RADIUS 0x7003 no inspect_detail withinSphere with a radius out of range
SCHEMA_REFUSED 0x8004 no correct_request the collection's declared schema refuses the value
ID_RESERVED 0x400A no correct_request an insert or create body that names _id (use put)
INVALID_ID 0x400B no correct_request an id that names no document's form
MISSING_FIELD 0xF008 no correct_request a required member is absent (a put with no id)
SECRET_FIELD 0x8006 no correct_request a filter, sort or projection names a secret field
ROW_STALE 0x4012 no inspect_detail a live-row write after another writer changed the document; the row now holds the stored document (code 0x4012)
ENGINE_GATE_HELD 0x4011 no inspect_detail a write to an engine-only SQL table from inside a query that reads the engine (code 0x4011)
TIMEOUT 0x3005 yes retry a write waited for a script transaction past the call's time and did not start
STORAGE_IO 0x1000 no inspect_detail a tree that could not be made, a write the store refused
SQL_FAILED 0xB006 no correct_request the engine failed to run the chain
INTERNAL 0x0106 no inspect_detail a refusal with no cause of its own (a fault on the Substrio side)
UniqueError no a write a unique field refuses; code is 'unique', with collection, field and value
RangeError no kv.getInt of a stored integer a number cannot hold, an $inc past int64 or the double range
NotFoundError no firstOrFail() found nothing

Not covered

function.eval runs Lua, not JavaScript; its failures are its own error bodies with no frames. It is not in the current release (Lua comes with the Redis frontend, SUBSTRIO_BUILD_REDIS): there it answers NOT_IMPLEMENTED (0x0002, HTTP 501), and substrio.lua.eval throws "substrio.lua.eval: Lua scripting is not in this build".