SQLite WAL journal mode corrupts runtime DBs (logs_2, state_5, …) when CODEX_HOME is on a network filesystem (NFS)

Open 💬 1 comment Opened Jul 2, 2026 by dkhokhlov

What version of Codex CLI is running?

codex-cli 0.137.0

What subscription do you have?

Business

Which model were you using?

gpt-5.4-mini

What platform is your computer?

Linux 6.12.94+deb13-amd64 x86_64 unknown

What terminal emulator and version are you using (if applicable)?

ssh

Codex doctor report

report.json

What issue are you seeing?

When CODEX_HOME (~/.codex) is on an NFSv4.2 mount, codex's runtime SQLite databases periodically corrupt. logs_2.sqlite is hit most often (it is the high-volume trace/feedback writer), but the same root cause affects all four runtime DBs (state_5.sqlite, logs_2.sqlite, goals_1.sqlite, memories_1.sqlite) because they all open with PRAGMA journal_mode=WAL.

Observed: PRAGMA integrity_check returns database disk image is malformed (DB header corruption); codex's auto-recovery moves the damaged DB into ~/.codex/db-backups/ and rebuilds a fresh one. With logs_2 this is merely annoying (ephemeral, recreatable); with state_5 it degrades the resume index (codex falls back to the JSONL rollout scan, so data isn't lost, but the index is rebuilt repeatedly).

Relevant mount: mini7:/mnt/home on /home type nfs4 (rw,relatime,vers=4.2,hard,proto=tcp,local_lock=none,sec=sys). CODEX_HOME is shared across multiple hosts (codex run from different SSH sessions/machines against the same home).

What steps can reproduce the bug?

  1. Put CODEX_HOME on NFSv4 (e.g. ~/.codex under an NFSv4.2 mount, local_lock=none, shared by ≥1 host).
  2. Run codex normally over time / from more than one host against the same CODEX_HOME.
  3. After some uptime, inspect the runtime DBs:

sqlite3 ~/.codex/logs_2.sqlite 'PRAGMA integrity_check;'
sqlite3 ~/.codex/state_5.sqlite 'PRAGMA integrity_check;'

  1. Expected to eventually return database disk image is malformed (header) rather than ok.
  2. Equivalently, observe codex auto-creating ~/.codex/db-backups/<ts>/ and rebuilding the DB on startup.

Root cause is in codex-rs/state/src/runtime.rs::base_sqlite_options, which hardcodes SqliteJournalMode::Wal for every runtime DB.

What is the expected behavior?

Runtime SQLite DBs should not corrupt when CODEX_HOME is on a network filesystem. SQLite's own documentation states WAL must not be used on network filesystems (mmap'd -shm is not coherent across
NFS clients — https://www.sqlite.org/wal.html, §"Use of WAL with Network Filesystems"). Codex should use a rollback journal mode (e.g. Truncate) on NFS and keep Wal only on local filesystems.

Additional information

Root cause. SQLite WAL coordinates readers/writers through a mmap'd shared-memory index file (*-shm). On NFS each client has its own page-cache view of -shm; mmap coherence is not guaranteed
across clients, so a WAL checkpoint can write wrong data back to the DB → header corruption. This is independent of fcntl locking — SQLite's POSIX byte-range locks work on NFSv4; the failure is
the separate mmap'd index. Rollback journal modes (Delete/Truncate) create no -shm, so they don't have this exposure.

Empirical verification (NFSv4.2 host, sqlite 3.46.1):

  • fcntl byte-range locks are enforced on this mount (local_lock=none → stateful server-side NFSv4 locks); a conflicting LOCK_EX|LOCK_NB gets EAGAIN.
  • WAL creates -shm while a connection is open; Delete/Truncate create no -shm.
  • Delete/Truncate on NFS: 4 concurrent writers × 200 rows = 800 rows, 0 database is locked errors, integrity_check=ok.
  • Latency on NFS, batched 1000 rows/txn (codex's log_db.rs flush path): WAL 0.0041 s, Truncate 0.0041 s (== WAL), Delete 0.0055 s. Rollback modes are effectively free vs WAL on the batched path.

Proposed fix. In base_sqlite_options, choose journal mode from the DB path's filesystem instead of hardcoding Wal:

  • Linux: statfs(2), compare f_type to NFS_SUPER_MAGIC (0x6969).
  • macOS: statfs, check MNT_NFS in f_flags.
  • Platforms without a portable statfs (Windows, BSD, …): fall back to Wal (current behavior — never worse than today).
  • statfs error or an otherwise-unclassifiable mount: bias toward the NFS-safe mode (Truncate) rather than silently keeping WAL, since this is a corruption-prevention fix.

On NFS use SqliteJournalMode::Truncate (rollback journal, no -shm, NFS-safe; == WAL on the batched path, less metadata churn than Delete because it truncates the journal instead of create+unlink per commit). On local filesystems keep Wal. The decision can be factored into a pure helper for unit testing (the statfs call can't be exercised in CI without an NFS mount). This fixes the corruption for all runtime DBs, not just logs_2.

Current workaround. A wrapper deletes logs_2.sqlite{,-wal,-shm} on each codex startup so every session starts with a clean WAL state. This works only because logs_2 is ephemeral; it does not protect state_5/goals_1/memories_1, and is not a source-level fix.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗