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 = TrueView on GitHub (pinned to 06cb6987f0)
Solutions
- Restore BOTH `-wal` and `-shm` files from the backup/copy so the set is complete, then reopen read-only.
- Or restore the palace with NO sidecars at all (only the main db, after the writer exited cleanly with a checkpoint).
- 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.
- 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
- Always stop the writer cleanly (finish hooks, graceful shutdown) before copying a palace.
- Backup tools must copy db, -wal, AND -shm together, or none of the sidecars.
- Never snapshot a palace directory while a mining run is active.
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
- collection was built with embedder {stored.model_name!r} but
- backend does not support facet_counts
- backend does not support maintenance kind {kind!r}
- update requires at least one of documents, metadatas, embedd
- {type(self).name} does not advertise supports_namespace_isol
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/d66f1fb424331d0a.
Report an issue: GitHub.