From 817633bc5d02c137cba1e1affc06cd151a683ec0 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Mon, 27 Apr 2026 06:43:52 -0700 Subject: [PATCH] feat(backup): exclude SQLite WAL/SHM/journal sidecars (#16576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- hermes_cli/backup.py | 8 ++++++++ tests/hermes_cli/test_backup.py | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/hermes_cli/backup.py b/hermes_cli/backup.py index 838af0ca12..05a9994a2d 100644 --- a/hermes_cli/backup.py +++ b/hermes_cli/backup.py @@ -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) diff --git a/tests/hermes_cli/test_backup.py b/tests/hermes_cli/test_backup.py index fc66308716..0932bfdf72 100644 --- a/tests/hermes_cli/test_backup.py +++ b/tests/hermes_cli/test_backup.py @@ -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"))