fix(datastore): auto-recover malformed peewee SQLite on startup - #154
TimeToBuildBob wants to merge 11 commits into
Conversation
Preserve a corrupt peewee-sqlite.v2.db as <path>.corrupt-<UTC> and replace it with a recovered copy so aw-server can start instead of restart-looping. Uses sqlite3 .recover when sqlite_dbpage is available; otherwise a sanitized .bail-off .dump (Debian/Ubuntu sqlite3 is built without dbpage). Reconstructs any eventmodel.bucket_id rows missing from bucketmodel. Disable with AW_SQLITE_AUTO_RECOVER=0. Git-Session-Id: 08b7
|
Windows CI has no sqlite3.exe, so CLI .recover/.dump never ran and the xor-corrupt fixture failed. Copy schema plus surviving rows through the stdlib sqlite3 module, reopening poisoned connections after DatabaseError. Git-Session-Id: 08b7
Git-Session-Id: 08b7
Create bucketmodel when a corrupt dump salvages eventmodel but omits the bucket catalog, then refuse replacement if events would still be unreachable. Preserve the original file mode on os.replace so recovery does not widen local read access under a permissive umask.
|
@greptileai review |
Windows leaves non-empty -wal/-shm next to the .corrupt-<UTC> copy, so glob(path + '.corrupt-*') matched three files and tripped assert len==1. Git-Session-Id: 08b7
ActivityWatch peewee SQLite auto-recover: restart loops on SQLITE_CORRUPT while events are still readable. Portable fallback when sqlite_dbpage is missing, dump ROLLBACK trap, open PR ActivityWatch/aw-core#154. Git-Session-Id: e09f
Git-Session-Id: 5f4c1144-c5d9-5e51-a701-52c1c2fa45a9
|
@greptileai review |
Git-Session-Id: 5f4c1144-c5d9-5e51-a701-52c1c2fa45a9
Git-Session-Id: 5f4c1144-c5d9-5e51-a701-52c1c2fa45a9
|
@greptileai review |
|
CI-green and mergeable (Greptile 5/5) — waiting only on a maintainer click. This PR is ready to merge, but the bot has pull-only access to this repo and can't self-merge — surfacing it here so it isn't lost. The monitoring loop will stop re-flagging it now that this note is posted. |
🤖 AI code reviewSafe to merge — 2 findings disposed (rejected)Updated after inline dispositions on finding threads — this is the current state; the verdict below is frozen at review time and is kept as the historical record of that pass. Finding disposition
Not safe to merge — 2 P1 openConfidence 2/5 ℹ️ Consensus was degraded on this run: 2 of 3 passes answered, so findings were filtered at 2-of-2 agreement rather than 2-of-3 — less filtered than usual; 2 of 3 fan-out jobs answered, so the findings above were filtered against less evidence than the threshold assumes. 3 findings · ❌ 2 P1 ·
|
| commit | score | findings | engine | when |
|---|---|---|---|---|
c544da7d2614 |
2/5 | 6 | llm | 2026-09-16 09:46 UTC |
9f76f5b8eb97 |
4/5 | 4 | llm | 2026-09-16 12:01 UTC |
1756bf932b62 |
4/5 | 3 | llm | 2026-09-16 13:22 UTC |
Reviewed 74a59ebae7b9 · openrouter/deepseek/deepseek-v4-flash-0731 · llm (in-band) engine · 308s · about this reviewer
Maintainer commands
@TimeToBuildBob review (own line) — fresh review · @TimeToBuildBob fix — a worker acts on the findings. Once per comment; 👀 = received.
O_EXCL + microsecond timestamps so a same-second retry cannot overwrite the preserved original. _counts swallows OSError so a post-replace stat/connect hiccup cannot restart-loop a recovered server. Git-Session-Id: cdb864f9-bb65-5b97-b145-103ee098a511
|
@greptileai review |
- Skip uncreatable tables in _recover_with_python instead of discarding all previously copied rows when one DDL fails. - Catch an unreadable bucketmodel during missing-bucket reconstruction so a recovered events file is not thrown away. Regression tests for both. Git-Session-Id: 37f48743-f7f9-53f3-b679-f8e58bb505d0
sqlite3 .dump comments of the form `/****** CORRUPTION ERROR *******/` were matched as a substring, so an INSERT whose payload contained those words was dropped. Drop comment lines only. After a .recover timeout, communicate() had no timeout and could hang PeeweeStorage.__init__. Bound the post-kill reaps. Git-Session-Id: 9fd15790-8aac-53c3-82f6-d6dd692d66c2
Problem
aw-server(Peewee/SQLite) can enter a restart loop when the on-diskpeewee-sqlite.v2.dbisSQLITE_CORRUPT(database disk image is malformed).PRAGMA quick_checkfails, the process exits, and the bundled restart actionhits the same file again. The database is often still partially readable, and
SQLite's recovery tools can salvage events.
Change
On
PeeweeStorageinit, if the file failsPRAGMA quick_check:<path>.corrupt-<UTC>(plus-wal/-shmif present).sqlite3 .recoverwhensqlite_dbpageis available (typical on macOS).sqlite3 .bail off .dumpand rewriteROLLBACK→COMMIT.Debian/Ubuntu sqlite3 is commonly built without
SQLITE_ENABLE_DBPAGE_VTAB,so
.recoverfails there withno such table: sqlite_dbpage.eventmodel.bucket_idrows missing frombucketmodelasrecovered-<key>so the datastore can open after a dump that salvaged eventsbut not bucket metadata.
PRAGMA quick_check. WAL/SHM on the live path are removed first so the oldlog cannot be applied to the new file.
Disable with
AW_SQLITE_AUTO_RECOVER=0. If recovery cannot run (nosqlite3CLI, or both strategies fail), the original file is left in place and the error
includes the manual commands.
Tests
tests/test_sqlite_recover.py:AW_SQLITE_AUTO_RECOVER=0raises without copyingROLLBACKand drops corruption markersFull suite: 201 passed, 2 skipped.
Notes
This does not change the on-disk schema of a healthy database. Recovered bucket
rows may be named
recovered-<key>when the bucket page was the corrupt one;events are kept.