Errors
Last updated 29 October 2026
The error classes the package throws, each kind, when it happens and what to do.
Every class extends UrsulaError and is exported by @tonbo/sqlite-ursula and /tonbo; TonboError by /tonbo alone. Any other error, such as a constraint violation or a syntax error, is node:sqlite’s, unchanged. Symptoms and their fixes are on Troubleshooting.
UrsulaError
class UrsulaError extends Error {
readonly kind: UrsulaErrorKind;
readonly action: "retry" | "reopen" | "stop" | "fix";
readonly cause?: unknown;
readonly code?: string; // node:sqlite's, when cause is its error
readonly errcode?: number;
readonly errstr?: string;
}kind names what failed and action what to do: branch on action. cause is the error the failure surfaced as, usually SQLite’s, whose code, errcode and errstr are copied, so code that reads them keeps working. Network failures never throw from statements or synced(): they wait.
import { UrsulaError } from "@tonbo/sqlite-ursula/tonbo";
try {
db.prepare("INSERT INTO notes(body) VALUES (?)").run(body);
} catch (error) {
if (!(error instanceof UrsulaError)) throw error; // a constraint, a syntax error
if (error.action === "retry") await db.synced(); // then run the transaction again
else throw error; // reopen, stop or fix
}Actions
UrsulaErrorAction. The extension names each failure’s kind, action and class, the error to throw, through ursula_classify since 0.11.2, so a kind this page does not list, from a newer extension, carries the extension’s action; 0.11.1 said fix for such a kind. Messages are the extension’s too.
| Action | What to do |
|---|---|
retry | Nothing was lost and the database stays usable. After backlog_full, await synced() and run the transaction again; after a failed open(), call it again later. |
reopen | Close every connection of the database and open() it again. |
stop | Another owner took the stream over. Stop using the database, and never reopen it in a loop: opening it takes the stream back. |
fix | It cannot succeed as asked. Fix the URL, the setup, the stream, the key or the transaction, then open the database again. |
UrsulaErrorKind is every kind on this page: the union of WriteRefusedKind, ResetKind, UnavailableKind, SetupKind and TonboKind.
UrsulaWriteRefusedError
A write or COMMIT refused with nothing committed: SQLite rolled the whole transaction back, and the database stays usable. Its errcode is 7178, SQLITE_IOERR_AUTH, and status().refused keeps the last one.
| Kind | Action | When | What to do |
|---|---|---|---|
backlog_full | retry | More than maxPendingBytes of commits wait for the stream. | Await synced(), then run the transaction again. |
unsupported | fix | A write the extension does not capture: in EXCLUSIVE locking mode, or a commit that leaves WAL mode or 4 KiB pages. | Leave journal_mode, the page size and the locking mode as open() sets them. |
too_large | fix | The commit holds more bytes of pages than the commit limit (see Limits). | Split the transaction, or raise URSULA_VFS_MAX_COMMIT_BYTES. |
UrsulaResetError
The database was reset. Every statement of it throws this, reads included (errcode 266 for a read, 778 for a write), synced() rejects with it, and the reset listeners get it. See After a reset.
| Kind | Action | When | What to do |
|---|---|---|---|
fenced | stop | Another process opened the database and took the stream over. | Stop. Find the second writer before you open the database again. |
stale | reopen | Another owner wrote or claimed the stream since this cache's claim, found once a provisional open reached the stream, or the stream sat idle past the server's producer expiry. | Open it again: the new open takes over with the pending commits. |
rewound | reopen | The stream's history moved back past commits this database holds, such as a restore to an earlier point. | Open it again; the old cache goes to salvage/ when it holds data. |
deleted | fix | The stream was deleted. | Create the database again, or stop. |
local_damage | reopen | The host's disk failed the extension: a failed fsync, a lost WAL page or a log write it could not cut. | Check the disk, then open it again. |
stream_state | fix | The stream is in a state replication cannot continue on. | Fix the stream, then open it again. |
internal | reopen | An internal failure of the extension. | Open it again. |
UrsulaUnavailableError
open() found no trusted state in time: its signal fired while the stream, or for an open by name the Tonbo API, did not answer as it must. kind is the last condition it met, or cancelled when it met none. A token provider that handed out no token in time rejects the open with unavailable, its last error as the cause. From importSqlite() every kind says fix, since the stream may hold the import’s claim.
UnavailableKind also names wrong_cluster, but the package throws it as an UrsulaSetupError.
UrsulaSetupError
open(), resolveDatabase() or importSqlite() cannot succeed as asked, or a database that retired was asked for synced() or status(). Its message says what to change.
| Kind | Action | When | What to do |
|---|---|---|---|
invalid_argument | fix | An option, a file to import, or an answer of the Tonbo API the package cannot use, or an open by name with a second API key while the first key’s tokens serve (since 0.11.2). The message names which. | Fix what it names. For a second key, use one key per process (see One token per process). |
invalid_url | fix | Not a stream URL, or one that would get the API key’s tokens over plain http:// to a host other than loopback. | Use the database’s https:// URL. |
invalid_name | fix | An empty database name (since 0.11.1). | Pass the name (see Names). |
not_found | fix | The stream is missing. Over a cache that holds data, only recreate opens it. | Create the database (see Manage databases). |
in_use | retry | Another process on this host holds the stream's cache, or its binding under another cache root of this process still drains. | Retry once the other one has let go. |
bound_elsewhere | fix | The stream is open in this process under another cache root, with connections open. | Close it there first, or pass its dir. |
local_setup | fix | The cache root, a file in it, a file to import or the login file cannot be read or written, or the extension library is older than the package, which loadUrsulaVfs() refuses since 0.11.2. | Fix the path or its permissions, or load the library of the package’s release (see SQLITE_URSULA_VFS). |
stream_format | fix | The stream holds another content type: it is not a database's stream. | Check the URL. |
no_server_identity | fix | An answer without Ursula-Protocol and Ursula-Cluster-Id, such as a proxy’s. | Check the URL and anything in front of the server. |
protocol_mismatch | fix | A server of another release line. | Use a package release of the server’s line: the exact version these docs name, for Tonbo. |
wrong_cluster | fix | The cache was built on another deployment than the server's. | Pass another dir, or remove that stream’s cache. |
stream_state | fix | The stream refuses opens: an import runs on it, or one failed after it claimed the stream. | Wait for the import, or delete and create the database again. |
claim_races | retry | The claim lost to other processes claiming the stream at the same time. | Make sure one process opens it, then retry. |
not_bound | fix | The database retired: its last connection closed. | Open it again with open(). |
internal | reopen | An internal failure of the extension. | Open it again. |
TonboError
From /tonbo, since 0.11.1. A key, a login or a database to fix: the action is always fix. fix holds what to do, the variable to set or the command to run, and the message ends with it. requestId is the Tonbo API’s id of the answer that failed, when one did; quote it to support.
class TonboError extends UrsulaError {
readonly kind: TonboKind;
readonly fix: string;
readonly requestId: string | undefined;
}| Kind | When | fix |
|---|---|---|
missing_credentials | No apiKey, no TONBO_API_KEY and no login file. | Set TONBO_API_KEY, or run: tonbo login |
credentials_refused | The login file is a symlink, not a regular file, or open to group or others. It is never read. | Run: chmod 600 <path>, or replace the file |
invalid_api_key | The API refused the key (401), the token endpoint refused it a token, the key cannot read the database (403), TONBO_API_KEY holds no usable key, or the login expired or is not one this package reads. | Pass a valid apiKey, set TONBO_API_KEY, or Run: tonbo login, after the key’s source; for a 403, Use an API key that can read database "<name>" |
database_not_found | The key's account has no database of that name, or it is being deleted. | Run: tonbo database create <name> |