Errors
Last updated 3 November 2026
The error classes the package raises, each kind with its action, when it happens and what to do.
Every class derives from UrsulaError and is exported by sqlite_ursula. Which failure is which is the SQLite extension’s to say, so a kind this page does not list, from a newer extension, carries the extension’s own action. Any other error, such as a constraint violation or a syntax error, is sqlite3’s, unchanged. Symptoms and their fixes are on Troubleshooting.
UrsulaError
class UrsulaError(Exception):
kind: str
action: Action
fix: str | None
sqlite_errorcode: int | None
sqlite_errorname: str | None
def __init__(
self,
message: str,
kind: str,
action: Action,
*,
fix: str | None = None,
cause: BaseException | None = None,
) -> None: ...import sqlite_ursula
try:
db.execute("INSERT INTO notes(body) VALUES (?)", (body,))
db.commit()
except sqlite_ursula.UrsulaError as error:
if error.action == "retry":
db.synced() # then run the transaction again
else:
raise # reopen, stop or fixkind names what failed and action what to do. Network failures never raise from statements or synced(): they wait. An error that replaced a SQLite error keeps it as __cause__. Errors pickle across processes, as multiprocessing needs.
| Field | Type | What it holds |
|---|---|---|
kind | str | What failed: one of the kinds below, or one a newer extension names. |
action | Action | What to do: an Action. Branch on it. |
fix | str | None | How to fix a host the package cannot run on, for host_unsupported; None otherwise. |
sqlite_errorcode | int | None | The SQLite error code of the error it replaced, as sqlite3.Error carries it, or None. |
sqlite_errorname | str | None | Its name, such as SQLITE_IOERR_AUTH, or None. |
The package raises these itself; construct one only to test your handling:
| Parameter | Type | Default | What it does |
|---|---|---|---|
message | str | Required | The message, the extension's or the package's. |
kind | str | Required | What failed: one of the kinds below. |
action | Action | Required | What to do: an Action. |
fix | str | None | None | How to fix a host the package cannot run on, for host_unsupported. |
cause | BaseException | None | None | The error it replaces, kept as __cause__; its sqlite_errorcode and sqlite_errorname are copied. |
Action
Action is Literal["retry", "reopen", "stop", "fix"]:
| Action | What to do |
|---|---|
retry | Nothing was lost and the database stays usable. After backlog_full, call 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 writer took the database over. Stop using it, and never reopen it in a loop: opening it takes it back. |
fix | It cannot succeed as asked. Fix the URL, the host, the token, the database or the transaction, then open it again. |
UrsulaWriteRefusedError
class UrsulaWriteRefusedError(UrsulaError, sqlite3.OperationalError):
...A write or COMMIT refused with nothing committed: SQLite rolled the whole transaction back, and the database stays usable. Its sqlite_errorcode is 7178, SQLITE_IOERR_AUTH. It is also a sqlite3.OperationalError, so an except sqlite3.OperationalError clause keeps catching it.
| Kind | Action | When | What to do |
|---|---|---|---|
backlog_full | retry | More than max_pending_bytes of commits wait for Tonbo, such as during an outage. | Call db.synced(), then run the transaction again. See Troubleshooting. |
unsupported | fix | A write the database does not capture: one 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 transaction’s commit holds more bytes of pages than the commit limit (see Limits). | Split the transaction, or raise URSULA_VFS_MAX_COMMIT_BYTES. See Troubleshooting. |
UrsulaResetError
class UrsulaResetError(UrsulaError, sqlite3.OperationalError):
...The database was reset. Every statement of it raises this, reads included, synced() raises it, and the on_reset callbacks get it. It is also a sqlite3.OperationalError.
| Kind | Action | When | What to do |
|---|---|---|---|
fenced | stop | Another process opened the database and took it over. | Stop using it, and find the second writer before you open it again. Never reopen it in a loop: each open takes the database back. See Troubleshooting. |
stale | reopen | Another owner wrote or claimed the database since this cache's claim, or it sat idle past the server's producer expiry. | Close every connection of it and open it again: the new open takes over with the pending commits. See Troubleshooting. |
rewound | reopen | The database's history moved back past commits this cache holds, such as a restore to an earlier point. | Open it again; the old cache goes to salvage/ when it holds data. See Troubleshooting. |
deleted | fix | The database was deleted. | Create it again (see Manage databases), or stop. See Troubleshooting. |
local_damage | reopen | The host's disk failed: a failed fsync, a lost WAL page or a log write that could not be cut. | Check the disk, then open it again. See Troubleshooting. |
stream_state | fix | The database's stream is in a state replication cannot continue on. | Fix the stream, then open the database again. |
internal | reopen | An internal failure of the extension. | Open it again. |
UrsulaUnavailableError
class UrsulaUnavailableError(UrsulaError):
...open() or aopen() found no trusted state before its timeout: kind is the last condition it met, or cancelled when it met none. From import_sqlite() every kind says fix, since the database may hold the import’s claim.
UrsulaSetupError
class UrsulaSetupError(UrsulaError):
...It cannot succeed as asked: the message says what to change. Raised by an open, an import, set_token(), a gate, a LangGraph class, and by a database that retired or a process that forked.
| Kind | Action | When | What to do |
|---|---|---|---|
invalid_argument | fix | An argument the package cannot take: a value of the wrong type or range, autocommit=False, database or uri for sqlite3.connect, read_only with recreate, a connection open() did not make, or a file to import. The message names which. | Fix what the message names. |
invalid_url | fix | Not a database's URL. | Use the database’s https:// URL, as tonbo database show --json prints it. |
not_found | fix | The database does not exist. Over a cache that holds data, only recreate opens it. | Create the database (see Manage databases), wait until it is active, and open it again. See Troubleshooting. |
in_use | retry | Another process on this host holds the database's cache, or its binding under another dir of this process still drains. | Call open() again once the other one has let go. |
bound_elsewhere | fix | The database is open in this process under another dir, with connections open. | Close it there first, or pass the same dir. |
local_setup | fix | The cache directory, a file in it or a file to import cannot be read or written, or SQLite could not load the extension library, or the library is older than the package. | Fix the path or its permissions, or load the library of the package’s release (see SQLITE_URSULA_VFS). |
stream_format | fix | The URL names a stream that is not a database's. | Check the URL. |
no_server_identity | fix | An answer that did not come from Tonbo, such as a proxy's. | Check the URL and anything in front of it. |
protocol_mismatch | fix | A server of another release line. | Install the exact release these docs name. |
wrong_cluster | fix | The cache was built on another deployment than the one that answers. | Pass another dir, or remove that database’s cache. |
stream_state | fix | The database refuses opens: an import runs on it, or one failed after it claimed it. | Wait for the import, or delete and create the database again. |
claim_races | retry | The claim lost to other processes opening the database at the same time. | Make sure one process opens it, then call open() again. |
not_bound | fix | The database retired: its last connection closed. | Open it again with open(). |
host_unsupported | fix | This Python cannot run the package: its sqlite3 cannot load extensions or runs too old a SQLite, or the platform has no prebuilt library. | Use the Python the error’s fix names; python -m sqlite_ursula checks one (see Pythons that run it). |
forked | fix | A call in a child process forked after its parent opened a database or imported one. | Open databases after the fork, or use the spawn or forkserver start method. |
internal | reopen | An internal failure of the extension or of the package's events thread. | Open the database again; restart the process if it repeats. |
Other errors
TimeoutErrorfromsynced(),asynced(),gate()andagate()past theirtimeout, while the commits keep replicating.asyncio.CancelledErrorfrom anaopen(),asynced()oragate()you cancelled. A cancelledaopen()cancels its bind too.KeyboardInterruptfrom anopen()orsynced()that Ctrl-C interrupted.sqlite3.Errorfor everything else a statement raises, unchanged.
A library may wrap an error: gate() finds an UrsulaError in __cause__, SQLAlchemy’s .orig and an ExceptionGroup, so look there too before you branch on action.