Function errors

Read the typed record a function's throw leaves behind, with its parsed stack frames, by request, function or revision.

Docs menu

Keep the key out of output in examples: send it as Authorization: Bearer $SUBSTRIO_KEY (the app is the host, or X-Substrio-Tenant: <app>). Read the record with operation function.errors (MCP tool substrio_function_errors, GET /v1/_function_errors); the same record is carried on a failed function.invoke and a failed function.execute_inline (POST /v1/_exec).

The record

Every throw is recorded once, where it is thrown, as a typed entry in the app's function error log. The stack is parsed into frames at that moment; nothing later re-parses text.

{
  "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. A bad cursor, a zero limit, or since/until past what milliseconds hold answers INVALID_ARGUMENT (400) with the option named in detail.

On the failed call itself

A failed function.invoke answers the standard error envelope (error, code CF_EXECUTION_FAILED, status 500, detail the message) plus one more member, function_error: the entry above without its storage identity.

{"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 start at that entry, or read it back with request_id.

function.execute_inline records the same entry under the function id <app>/_exec, with a fresh request_id, and answers 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 host call that refuses (a db, chain, kv, ledger, files, cmd, queue or fetch call) throws an Error whose name is its code from the error table, so a script branches on e.name. The realm's own TypeError is kept for one fault only: an argument of a type the call does not take (instanceof TypeError holds). A wait that reaches the call's deadline turns whatever the call would throw into SubstrioTimeoutError, and the call answers 504 CF_WALL_TIMEOUT. Web APIs (fetch's own argument checks, crypto, streams, formData) throw what their standards say. The same codes over HTTP: api/errors.md.

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
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.