feat(backup): exclude SQLite WAL/SHM/journal sidecars (#16576)

The backup takes a consistent snapshot of each .db via sqlite3.backup(),
so shipping the live .db-wal / .db-shm / .db-journal alongside pairs the
fresh snapshot with stale sidecar state and produces a torn restore on
first open. Sidecars are transient and SQLite regenerates them on next
connection anyway.

This also trims multi-MB of junk from every zip — state.db-wal alone was
~9 MB here, doubled by the fact the WAL is the live write-ahead log, not
data.
This commit is contained in:
Teknium
2026-04-27 06:43:52 -07:00
committed by GitHub
parent 9692ce2072
commit 817633bc5d
2 changed files with 20 additions and 0 deletions

View File

@@ -45,6 +45,14 @@ _EXCLUDED_DIRS = {
_EXCLUDED_SUFFIXES = (
".pyc",
".pyo",
# SQLite sidecar files — the backup takes a consistent snapshot of ``*.db``
# via ``sqlite3.backup()``, so shipping the live WAL / shared-memory /
# rollback-journal alongside would pair a fresh snapshot with stale sidecar
# state and produce a torn restore on the next open. They're transient and
# regenerated on first connection anyway.
".db-wal",
".db-shm",
".db-journal",
)
# File names to skip (runtime state that's meaningless on another machine)

View File

@@ -103,6 +103,18 @@ class TestShouldExclude:
from hermes_cli.backup import _should_exclude
assert _should_exclude(Path("backups/pre-update-2026-04-27-063400.zip"))
def test_excludes_sqlite_sidecars(self):
"""SQLite WAL/SHM/journal sidecars must not ship alongside the
safe-copied .db — pairing a fresh snapshot with stale sidecar state
produces a torn restore."""
from hermes_cli.backup import _should_exclude
assert _should_exclude(Path("state.db-wal"))
assert _should_exclude(Path("state.db-shm"))
assert _should_exclude(Path("state.db-journal"))
assert _should_exclude(Path("memory_store.db-wal"))
# The .db itself is still included (and safe-copied separately)
assert not _should_exclude(Path("state.db"))
def test_includes_config(self):
from hermes_cli.backup import _should_exclude
assert not _should_exclude(Path("config.yaml"))