MemPalace/mempalace · error · BackendError

sqlite_exact read-only open found an incomplete WAL sidecar

Error message

sqlite_exact read-only open found an incomplete WAL sidecar set; open the palace after its writer exits cleanly or restore both the -wal and -shm files

What it means

Raised by `SQLiteExactBackend._connect_read_only`: exactly one of the two SQLite WAL sidecar files (`-wal`, `-shm`) exists. A live WAL pair is fine (mode=ro reads it), and no WAL at all is fine (opened immutable=1), but a lone `-wal` or lone `-shm` means the database was left in an inconsistent state — a writer crashed or was killed mid-checkpoint — and a read-only open cannot safely recover it because recovery would require writing.

Source

Thrown at mempalace/backends/sqlite_exact.py:909

        return os.path.join(palace_path, _DB_FILENAME)

    @staticmethod
    def _wal_sidecar_state(db_path: str) -> tuple[bool, bool]:
        return (
            os.path.isfile(f"{db_path}-wal"),
            os.path.isfile(f"{db_path}-shm"),
        )

    @staticmethod
    def _connect_read_only(db_path: str) -> tuple[sqlite3.Connection, bool]:
        """Open without creating WAL files while preserving an active WAL.

        Returns ``(connection, immutable)``. ``immutable`` is True when the
        database was clean (no WAL) and was opened with ``immutable=1``.
        """
        wal_exists, shm_exists = SQLiteExactBackend._wal_sidecar_state(db_path)
        if wal_exists != shm_exists:
            raise BackendError(
                "sqlite_exact read-only open found an incomplete WAL sidecar set; "
                "open the palace after its writer exits cleanly or restore both "
                "the -wal and -shm files"
            )

        db_uri = Path(db_path).resolve().as_uri()
        if wal_exists:
            # An active writer's uncheckpointed rows live in the WAL. With both
            # sidecars already present, mode=ro can read them without creating
            # filesystem state, including on a read-only mount.
            db_uri = f"{db_uri}?mode=ro"
            immutable = False
        else:
            # A clean WAL-mode database would otherwise make SQLite create new
            # -wal/-shm files while connecting. Immutable mode is safe here
            # only until a writer creates sidecars this connection would miss.
            db_uri = f"{db_uri}?mode=ro&immutable=1"
            immutable = True

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Restore BOTH `-wal` and `-shm` files from the backup/copy so the set is complete, then reopen read-only.
  2. Or restore the palace with NO sidecars at all (only the main db, after the writer exited cleanly with a checkpoint).
  3. Or open the palace once in read-write mode from the same machine — SQLite will recover/complete the WAL set — then close cleanly; read-only opens will work afterwards.
  4. Prevent recurrence: stop the writer cleanly (let hooks finish) before copying or backing up the palace directory.

Example fix

# before: backup captured db + -wal but not -shm
# ls palace/*.db* -> palace.db, palace.db-wal   (missing palace.db-shm)
backend.get_collection(palace, "drawers", read_only=True)  # BackendError

# after: complete the set (or drop both sidecars after clean close)
cp backup/palace.db-shm palace/palace.db-shm
backend.get_collection(palace, "drawers", read_only=True)
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def wal_sidecars_complete(db_path) -> bool:
    wal, shm = Path(f"{db_path}-wal"), Path(f"{db_path}-shm")
    return wal.is_file() == shm.is_file()  # both or neither

Try / catch

try:
    backend.get_collection(palace, "drawers", read_only=True)
except BackendError as e:
    if "incomplete WAL sidecar" in str(e):
        # open once read-write to let SQLite recover, then retry read-only
        backend_rw = SQLiteExactBackend()
        backend_rw.get_collection(palace, "drawers", create=True)
        backend_rw.close()
        backend.get_collection(palace, "drawers", read_only=True)
    else:
        raise

Prevention

When it happens

Trigger: Opening a palace read-only after the writing process was SIGKILLed/power-lossed between creating `-wal` and `-shm`; someone deleted or half-restored one sidecar in a backup/sync copy; copying a palace directory while a writer was active so only one sidecar got copied.

Common situations: Restoring a palace from a backup tool or cloud sync that skipped one sidecar; a crashed mining run followed by a read-only search; NFS/sync mounts that lose one file; snapshotting a live palace.

Related errors


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/d66f1fb424331d0a. Report an issue: GitHub.