Skip to content
Lakefront
Esc
↑↓navigate↵open⌘Jpreview
On this page

Errors

The full tagged-error catalog, covering engine errors, wire-only tags, and how each failure surfaces to a caller.

Every fallible call in Lakefront fails into one of two catalogs: engine errors, raised inside the engine and carried unchanged across the wire, and wire-only tags, synthesised by the transport or the server before a handler runs. This page lists both, with the fields and conditions behind each.

How failures surface

Engine methods and BoardClient’s mutation methods return Result<T, WireError> (from better-result): check isErr(), branch on error._tag, or call .unwrap() for the happy path.

const outcome = await board.insertMany(rows);
if (outcome.isErr()) {
  console.error(outcome.error._tag, outcome.error.message);
} else {
  console.log("committed at", outcome.value);
}

Inside engine.transaction(...), TransactionOps methods throw instead: the transaction absorbs the failure and rolls back, so there’s no Result for call sites inside the callback to forget to check.

The client’s builder reads (select, selectAll, db) also throw: Kysely’s Driver interface is exception-based, so LakefrontConnection.executeQuery is the one place a Result is deliberately unwrapped.

Underneath, transports raise RpcError (from lakefront/contract) for a non-200 response with no JSON envelope. createRpcClient’s retry loop always catches it internally and converts the final failure to a TransportFailed Result rather than letting it escape as a throw. See the client reference.

Engine errors

Raised by the engine; the same _tag crosses the wire unchanged as a WireError.

Tag Fields When
InvalidBoardId boardId, message A board id fails /^[A-Za-z0-9_]{1,64}$/u: board ids are interpolated into table names, so they’re validated rather than escaped.
EntityNotAllowed entity, message A placeholder bound to an entity outside the server’s allowlist: the board-isolation boundary refusing.
PlaceholderUnbound token, message A {{token}} placeholder either has an invalid name or reached execution still unbound.
LakeUnavailable message, cause? The lake couldn’t be attached: catalog unreachable, or the concurrent first-boot race exhausted its retries.
QueryFailed sql, message, cause? A query failed inside DuckDB. Carries the statement so the failure is diagnosable without re-running it.
SyncFailed boardId, from, to, message, cause? Applying the change feed to a replica failed. The replica is a cache, so recovery is always rebuild, never repair.
ReplicaUnavailable boardId, message, cause? A replica could not be materialised from the lake.
WriteFailed boardId, message, cause? A write did not commit. Distinct from a read failure: it may have landed, so it must not be retried blindly.
ReservedColumnName boardId, column, message A write named a column with the engine’s reserved _ prefix: those carry the version stamps that upsert ordering, compaction, and replica deletes all read.
ColumnTypeConflict boardId, column, columnType, message, cause? A value can’t be stored in the column’s current type and the engine won’t reshape the board silently. For example, a string written into a column inferred as double. Fix with an explicit widenColumn(board, column, to), naming the lowercase ColumnType vocabulary (see the schema guide).
BoardNotFound boardId, message The board doesn’t exist: reading one never written to, writing under autoCreate: "never", or touching one another node has since dropped.
BoardExists boardId, message createBoard on a board that’s already there.
UnknownColumn boardId, column, message A write named a column that doesn’t exist, under engine-wide columns: "declared" or a board created with strict: true. See createBoard in the engine reference.

Wire-only tags

Never raised by the engine: synthesised at the client transport or the server’s request handling, before (or instead of) a handler running.

Tag HTTP status When
Unauthenticated 401 Missing or invalid bearer token, when auth is on.
MissingBoardEnvelope 401 No x-lakefront-board header and the token doesn’t pin exactly one board.
Forbidden 403 Token’s claims don’t allow the procedure’s required verb on this board.
InvalidInput 400 Request body isn’t valid JSON, or fails the procedure’s input schema.
UnknownProcedure 404 <procedure> in /rpc/<procedure> isn’t in the contract.
MalformedResponse — (client-side) The response body didn’t decode into a valid envelope for the procedure that was called.
TransportFailed — (client-side) Every retry attempt failed at the transport level (network error, timeout, a non-JSON 5xx). See the retry policy in the wire contract.

Full status/tag mapping and the envelope discipline that keeps a ran-but-failed handler at 200 are covered in the wire protocol reference.

messageOf

function messageOf(cause: unknown): string;

Normalises a thrown value into a message without swallowing its type: cause instanceof Error ? cause.message : String(cause). Exported from both lakefront/contract and lakefront (core/errors.ts re-exports the contract’s copy so engine code keeps one import path).

Fixing the actionable ones

  • ColumnTypeConflict: a value doesn’t fit the column’s current type. Fix with widenColumn(board, column, to), using the lowercase ColumnType vocabulary ("text", "bigint", "double", …). See the engine reference and schema guide.
  • LakeUnavailable: the catalog Postgres or the lake storage is unreachable. See the troubleshooting guide.

Last updated on September 10, 2026

Was this page helpful?