> Fetch the index of every Tonbo docs page as Markdown at https://tonbo.dev/llms.txt

# 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](https://tonbo.dev/docs/databases/troubleshooting.md).

## UrsulaError

```python
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: ...
```

```python
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 fix
```

`kind` 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](#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](https://tonbo.dev/docs/reference/python/errors.md#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

```python
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`](#action-retry) | More than [max_pending_bytes](https://tonbo.dev/docs/reference/python/open.md#open-max-pending-bytes) of commits wait for Tonbo, such as during an outage. | Call `db.synced()`, then run the transaction again. See [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/writes.md#backlog-full). |
| `unsupported` | [`fix`](#action-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`](#action-fix) | The transaction’s commit holds more bytes of pages than the commit limit (see [Limits](https://tonbo.dev/docs/databases/limits.md#transaction)). | Split the transaction, or raise `URSULA_VFS_MAX_COMMIT_BYTES`. See [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/writes.md#too-large). |

## UrsulaResetError

```python
class UrsulaResetError(UrsulaError, sqlite3.OperationalError):
    ...
```

The database was reset. Every statement of it raises this, reads included, `synced()` raises it, and the [on_reset](https://tonbo.dev/docs/reference/python/connection.md#on-reset) callbacks get it. It is also a `sqlite3.OperationalError`.

| Kind | Action | When | What to do |
| --- | --- | --- | --- |
| `fenced` | [`stop`](#action-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](https://tonbo.dev/docs/databases/troubleshooting/reset.md#fenced). |
| `stale` | [`reopen`](#action-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](https://tonbo.dev/docs/databases/troubleshooting/reset.md#reset). |
| `rewound` | [`reopen`](#action-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](https://tonbo.dev/docs/databases/troubleshooting/reset.md#reset). |
| `deleted` | [`fix`](#action-fix) | The database was deleted. | Create it again (see [Manage databases](https://tonbo.dev/docs/databases/manage.md#create)), or stop. See [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/reset.md#reset). |
| `local_damage` | [`reopen`](#action-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](https://tonbo.dev/docs/databases/troubleshooting/reset.md#reset). |
| `stream_state` | [`fix`](#action-fix) | The database's stream is in a state replication cannot continue on. | Fix the stream, then open the database again. |
| `internal` | [`reopen`](#action-reopen) | An internal failure of the extension. | Open it again. |

## UrsulaUnavailableError

```python
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()](https://tonbo.dev/docs/reference/python/gate-and-import.md#import-sqlite) every kind says `fix`, since the database may hold the import’s claim.

| Kind | Action | When | What to do |
| --- | --- | --- | --- |
| `unreachable` | [`retry`](#action-retry) | No connection to Tonbo. | Check the network, then call open() again. See [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/open.md#unavailable). |
| `unavailable` | [`retry`](#action-retry) | Tonbo answered 5xx or no definite answer, or the database was still being created. | Call open() again later. See [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/open.md#unavailable). |
| `rate_limited` | [`retry`](#action-retry) | Tonbo answered 429. | Call open() again later. |
| `unauthorized` | [`fix`](#action-fix) | Tonbo answered 401: the token is missing, expired or revoked, or is a key that acts as an Agent and was not exchanged. | Pass a valid key or access token, and call [set_token()](https://tonbo.dev/docs/reference/python/gate-and-import.md#set-token) with each new one. See [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/open.md#unauthorized). |
| `payment_required` | [`fix`](#action-fix) | Tonbo answered 402. | See [When the balance runs out](https://tonbo.dev/docs/databases/pricing.md#zero-balance). See [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/writes.md#payment-required). |
| `forbidden` | [`fix`](#action-fix) | Tonbo answered 403: the key does not reach this database, or is read-only and the open claims it. | Check what the key reaches (see [What a key can reach](https://tonbo.dev/docs/platform/api-keys.md#key-scope)), or open with `read_only=True`. See [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/writes.md#forbidden). |
| `cancelled` | [`retry`](#action-retry) | The `timeout` passed, or the open was cancelled, before it met any condition. | Call `open()` again, with a longer `timeout`. |

## UrsulaSetupError

```python
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`](#action-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`](#action-fix) | Not a database's URL. | Use the database’s `https://` URL, as `tonbo database show --json` prints it. |
| `not_found` | [`fix`](#action-fix) | The database does not exist. Over a cache that holds data, only `recreate` opens it. | Create the database (see [Manage databases](https://tonbo.dev/docs/databases/manage.md#create)), wait until it is active, and open it again. See [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/open.md#not-created). |
| `in_use` | [`retry`](#action-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`](#action-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`](#action-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](https://tonbo.dev/docs/reference/nodejs/environment.md#extension)). |
| `stream_format` | [`fix`](#action-fix) | The URL names a stream that is not a database's. | Check the URL. |
| `no_server_identity` | [`fix`](#action-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`](#action-fix) | A server of another release line. | Install the exact release these docs name. |
| `wrong_cluster` | [`fix`](#action-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`](#action-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`](#action-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`](#action-fix) | The database retired: its last connection closed. | Open it again with open(). |
| `host_unsupported` | [`fix`](#action-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](https://tonbo.dev/docs/reference/python.md#hosts)). |
| `forked` | [`fix`](#action-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`](#action-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

- `TimeoutError` from `synced()`, `asynced()`, `gate()` and `agate()` past their `timeout`, while the commits keep replicating.
- `asyncio.CancelledError` from an `aopen()`, `asynced()` or `agate()` you cancelled. A cancelled `aopen()` cancels its bind too.
- `KeyboardInterrupt` from an `open()` or `synced()` that Ctrl-C interrupted.
- `sqlite3.Error` for everything else a statement raises, unchanged.

A library may wrap an error: [gate()](https://tonbo.dev/docs/reference/python/gate-and-import.md#gate) finds an `UrsulaError` in `__cause__`, SQLAlchemy’s `.orig` and an `ExceptionGroup`, so look there too before you branch on `action`.
