Hmbown/CodeWhale · error

failed to enable WAL for

Error message

failed to enable WAL for {}: SQLite retained journal mode {configured_mode}

What it means

The state store requires SQLite databases to run in WAL (write-ahead logging) journal mode for concurrent readers. configure_connection issues PRAGMA journal_mode=WAL and checks the mode SQLite actually switched to; some conditions make SQLite silently keep the old mode (e.g. 'delete'), and the code then bails rather than continue with degraded durability.

Solutions

  1. Move the database file to a local filesystem (WAL needs shared memory, unavailable on NFS/SMB).
  2. Close other connections/processes holding the DB before opening, so the mode switch isn't blocked.
  3. Run PRAGMA journal_mode; manually to see the retained mode and diagnose.
  4. Ensure no explicit transaction is open on the connection when the pragma runs.
  5. Check the SQLite version supports WAL (>= 3.7.0).

Example fix

// before: db on network share
let db = StateDb::open("/mnt/nfs/codewhale/state.db")?; // fails WAL
// after: local data dir
let db = StateDb::open("~/.local/state/codewhale/state.db")?;
Defensive patterns

Strategy: fallback

Validate before calling

// Detect unsuitable filesystems before opening the db
let meta = std::fs::metadata(db_path.parent().unwrap())?;
// e.g. reject known network mount points or check /proc/mounts for nfs/cifs

Type guard

fn is_local_path(p: &Path) -> bool {
    p.starts_with("/home") || p.starts_with("/Users") || p.starts_with("/tmp")
}

Try / catch

match StateDb::open(db_path) {
    Ok(db) => db,
    Err(e) if e.to_string().contains("retained journal mode") => {
        eprintln!("WAL unavailable on {db_path:?}; use a local disk");
        StateDb::open(local_fallback_path())?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Opening/initializing the database when SQLite refuses or fails to switch journal_mode to WAL — commonly because another connection holds the database in a different mode, the file is on a network filesystem that doesn't support WAL shared memory, or a transaction is active on the connection.

Common situations: Database on NFS/SMB/network mounts (classic WAL failure); another process/connection with an open transaction or lock; copying a non-WAL db file over the live one mid-run; very old SQLite builds; read-only filesystem (pragma_update itself would fail first).

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/25bd048866555b51. Report an issue: GitHub.

Appendix: source

Thrown at crates/state/src/lib.rs:345

        conn.busy_timeout(std::time::Duration::from_secs(5))
            .with_context(|| format!("failed to set busy_timeout for {}", db_path.display()))?;
        conn.pragma_update(None, "foreign_keys", "ON")
            .with_context(|| format!("failed to enable foreign keys for {}", db_path.display()))?;

        // WAL persists in the database header, so established stores need no
        // write-like journal transition on every process start. Fresh or
        // explicitly downgraded stores still transition once, and we verify
        // SQLite accepted the requested mode instead of silently retaining the
        // previous one (for example on an unsupported VFS).
        let journal_mode: String = conn
            .pragma_query_value(None, "journal_mode", |row| row.get(0))
            .with_context(|| format!("failed to read journal mode for {}", db_path.display()))?;
        if !journal_mode.eq_ignore_ascii_case("wal") {
            let configured_mode: String = conn
                .pragma_update_and_check(None, "journal_mode", "WAL", |row| row.get(0))
                .with_context(|| format!("failed to enable WAL for {}", db_path.display()))?;
            if !configured_mode.eq_ignore_ascii_case("wal") {
                anyhow::bail!(
                    "failed to enable WAL for {}: SQLite retained journal mode {configured_mode}",
                    db_path.display()
                );
            }
        }
        Ok(())
    }

    /// Returns the filesystem path of the underlying SQLite database.
    pub fn db_path(&self) -> &Path {
        &self.db_path
    }

    fn conn(&self) -> Result<MutexGuard<'_, Connection>> {
        // Poisoning means a panic mid-operation; any open transaction was
        // rolled back when it dropped, but surface the condition rather than
        // silently continuing on a connection whose state we can't vouch for.
        self.conn

View on GitHub (pinned to 73e0f67d83)