fix(datastore): gracefully handle VACUUM failure in compiled binary (swamp-club#288)#1340
Conversation
…swamp-club#288) CatalogStore.vacuum() now catches errors and returns a boolean instead of crashing when SQLITE_LIMIT_ATTACHED=0 in the canary Deno runtime. The compact command reports vacuumSkipped in its output so callers know VACUUM was skipped while WAL checkpoint still succeeded. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
- The log-mode warning
VACUUM skipped (runtime limitation) — WAL checkpoint still reclaimed spaceis clear and reassuring, but "runtime limitation" is vague for users who may not know what SQLITE_LIMIT_ATTACHED is. A slightly more specific message likeVACUUM skipped (SQLite restriction in this runtime)would be marginally clearer — minor, not blocking.
Verdict
PASS — Graceful degradation with correct UX: exits 0, emits a warn-level message in log mode, and exposes vacuumSkipped in JSON output. Field naming is consistent (camelCase, matches walPagesTotal, dbBytesReclaimed). No flags added, no help text needed.
There was a problem hiding this comment.
Code Review
Blocking Issues
None.
Suggestions
- Broad catch in
CatalogStore.vacuum()(src/infrastructure/persistence/catalog_store.ts:574): The catch block swallows all errors, not just the knownSQLITE_LIMIT_ATTACHED=0case. If a future failure has a different root cause (e.g. disk full, permission denied), it will be silently downgraded to a warning. Consider narrowing the catch to match the specific error message/code for the canary runtime limitation, re-throwing unexpected errors. This is non-blocking since the error is logged and the caller knows vacuum was skipped, but it would improve debuggability.
Overall this is a clean, well-structured fix. Layer placement follows DDD conventions correctly — infrastructure concern stays in persistence, the application service (libswamp) abstracts it via the DatastoreCompactDeps interface, and the presentation layer imports only from libswamp/mod.ts. Test coverage is good for both the happy path and the degraded path.
There was a problem hiding this comment.
Adversarial Review
Critical / High
None found.
Medium
-
Overly broad catch in
vacuum()—src/infrastructure/persistence/catalog_store.ts:574The catch block swallows all errors from
VACUUM, but the intent (per the docstring and PR description) is to handle specificallySQLITE_LIMIT_ATTACHED=0in the canary Deno runtime. In practice this also silently downgrades:SQLITE_CORRUPT— database corruptionSQLITE_IOERR— disk I/O failureSQLITE_FULL— disk fullSQLITE_BUSY— database locked by another connection
Breaking scenario: The disk fills up mid-operation.
VACUUMfails withSQLITE_FULL. The user sees "VACUUM skipped (runtime limitation)" and the command exits 0, masking a real infrastructure problem. The warn-level log line includes the error object, so it's not fully silent, but the UX (exit 0, "runtime limitation" language) is misleading for these cases.Why this is MEDIUM, not HIGH:
VACUUMis a read-compact-rewrite operation — its failure never corrupts data or loses writes. The WAL checkpoint (the actually important step) runs first and is unaffected. The worst outcome is the user doesn't realize their disk is full until the next write fails.Suggested fix: Check the error message for the known canary string (e.g.
SQLITE_LIMITorLIMIT_ATTACHED) and only returnfalsefor that. Re-throw everything else. Or at minimum, adjust the warning to include— if this is unexpected, check disk and database healthso the user has a breadcrumb.
Low
-
Test only covers happy path for
CatalogStore.vacuum()—src/infrastructure/persistence/catalog_store_test.ts:583-593The new
CatalogStoretest asserts thatvacuum()returns a boolean and doesn't throw, but only exercises the success path (a working in-memory DB). There is no test that forcesVACUUMto fail and assertsfalseis returned. Thecompact_test.tsdoes cover this at the deps-injection level (stubbingvacuum: () => false), but the actual catch-and-return-false code path inCatalogStoreis untested. This is low severity because the logic is trivial (try/catch returning a boolean), and the Deno test environment may not make it easy to trigger SQLITE_LIMIT_ATTACHED=0.
Verdict
PASS — The change is well-scoped, the type plumbing is correct across all layers, the new interface field is properly additive, and the only caller is correctly updated. The broad catch is a pragmatic trade-off for a non-destructive operation, though narrowing it would be ideal as a follow-up.
Summary
CatalogStore.vacuum()now catches errors and returns abooleaninstead of throwing whenSQLITE_LIMIT_ATTACHED=0in the canary Deno runtimeDatastoreCompactDatagains avacuumSkippedfield so callers know VACUUM was skippedvacuumSkippedin JSON outputTest Plan
datastoreCompactwithvacuum()returningfalse— assertsvacuumSkipped: trueCatalogStore.vacuum()— asserts it returns a boolean and does not throw{"vacuumSkipped": true}instead of crashingCloses swamp-club#288