fix(state): warn when configured journal_mode=delete is overridden by on-disk WAL - #85609
Conversation
… on-disk WAL When database.journal_mode=delete is configured but the on-disk DB is already WAL, apply_wal_with_fallback honors the never-live-downgrade rule and keeps WAL. That is correct (a live downgrade under open connections causes mixed-mode corruption), but the operator's configured mode silently has no effect, and on a WAL-incompatible filesystem (virtiofs/NFS/SMB) the DB then corrupts on the next crash/sleep exactly what they configured to prevent. Two code paths return WAL in this situation; both now emit a once-per-process- per-db_label ERROR telling the operator the config did not apply and they must convert the DB header offline (stop connections, PRAGMA journal_mode=DELETE): 1. The WAL-reset-vulnerable path (_apply_delete_for_wal_reset_bug): previously warned only about the vulnerability with an "upgrade SQLite" remedy, which does not help when the real cause is the filesystem. Emitted after that warning so the actionable message is last. 2. The read-only probe path (non-vulnerable runtime): previously returned WAL with no signal at all. The never-live-downgrade behavior is unchanged (existing test now also asserts the warning). New tests cover both paths, the per-db_label dedup, and the require_wal=True edge case. Real-world impact: a Hermes deployment with state.db on a Podman virtiofs bind-mount (or any NFS/SMB home) that upgrades across a version where WAL was the default, then sets journal_mode=delete, sees no corruption protection until the DB header is converted. This makes the gap visible. See NousResearch#68545.
fix(state): warn when configured journal_mode=delete is overridden by on-disk WAL
|
|
@C:/Users/admin/AppData/Local/Temp/opencode/review-85609.md |
|
Addressed the review from @Enough1122 point by point (verified against the code on this branch):
|
|
@Enough1122 heads-up: your later comment (13:18 UTC, #issuecomment-5307630832) contains only a local Windows temp path, "C:/Users/admin/AppData/Local/Temp/opencode/review-85609.md", instead of review content. Looks like the review automation pasted the file path where the file contents belonged, so whatever that second review said never landed. The 13:10 review arrived intact. |
|
Cross-linking from #90950: this is exactly the operator-signal gap we hit during a corruption incident — a |
Follow-up review of the current head (the earlier second-review comment only contained a stray local file path — reposting the content properly):
No blocking issues — this is ready from my side. |
|
Thanks for the heads-up — confirmed, that comment was automation pasting a local path instead of the content. The full second review is now posted above (#85609 (comment)). |
Closes #85608.
What
When
database.journal_mode: deleteis configured but the on-disk DB is already WAL,apply_wal_with_fallback()honors the never-live-downgrade rule and keeps WAL. That is correct (a live downgrade under open connections causes mixed-mode corruption), but the operator has no signal that their configured mode had no effect, and on a WAL-incompatible filesystem (virtiofs/NFS/SMB) the DB then corrupts on the next crash/sleep — exactly what they configured to prevent.This PR adds a once-per-process-per-db_label ERROR telling the operator the config did not apply and they must convert the DB header offline (
PRAGMA journal_mode=DELETEwith no open connections). See #85608 for the full motivation and the two affected code paths.Changes
_log_configured_delete_overridden_once(db_label)helper (mirrors the existing_log_wal_fallback_once/_log_wal_reset_bug_onceidiom: module-level set + lock, deduped per process per db_label, ERROR level).delete:_apply_delete_for_wal_reset_bug): previously warned only about the vulnerability with an "upgrade SQLite" remedy that does not help when the cause is the filesystem. The new warning fires AFTER that one, so the actionable message is last.Tests
All behavioral (real
sqlite3on tmp files,caplogassertions), no mocks at the boundary:test_configured_delete_never_live_downgrades_existing_wal(updated): vulnerable path keeps WAL AND warns.test_configured_delete_overridden_warns_on_non_vulnerable_runtime_too: probe path also warns (the previously fully-silent case), with dedup.test_configured_delete_overridden_warning_fires_once_per_db: dedup to one per process per db_label.test_configured_delete_with_require_wal_and_existing_wal_returns_wal: pins therequire_wal=True+ configured=delete + on-disk WAL edge (returns wal, warns, noWalUnsupportedError).Full WAL test suites pass (
test_journal_mode_config.py,test_hermes_state_wal_fallback.py,test_sqlite_wal_reset_gate.py) in both run orders.Notes
delete.hermes doctorcheck or ahermes dbmigration helper as a follow-up if maintainers want either (mentioned in database.journal_mode: delete silently ignored when the on-disk DB is already WAL (no warning, no doctor signal) #85608), but kept this PR minimal.