benbjohnson/litestream · error
enable wal failed, mode=%q
Error message
enable wal failed, mode=%q
What it means
Litestream requires the database to be in WAL journal mode; during init it runs `PRAGMA journal_mode = wal;` and verifies the returned mode is exactly "wal". SQLite accepts the PRAGMA but may keep a different mode (e.g. "delete", "truncate", or returning the old mode when the switch fails), so litestream fails explicitly rather than replicating a non-WAL database it cannot monitor correctly.
Source
Thrown at db.go:1080
// Ensure database is closed if init fails.
// Initialization can retry on next sync.
defer func() {
if err != nil {
_ = db.releaseReadLock()
db.db.Close()
db.f.Close()
db.db, db.f = nil, nil
}
}()
// Enable WAL and ensure it is set. New mode should be returned on success:
// https://www.sqlite.org/pragma.html#pragma_journal_mode
var mode string
if err := db.db.QueryRowContext(ctx, `PRAGMA journal_mode = wal;`).Scan(&mode); err != nil {
return err
} else if mode != "wal" {
return fmt.Errorf("enable wal failed, mode=%q", mode)
}
// Create a table to force writes to the WAL when empty.
// There should only ever be one row with id=1.
if _, err := db.db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS _litestream_seq (id INTEGER PRIMARY KEY, seq INTEGER);`); err != nil {
return fmt.Errorf("create _litestream_seq table: %w", err)
}
// Create a lock table to force write locks during sync.
// The sync write transaction always rolls back so no data should be in this table.
if _, err := db.db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS _litestream_lock (id INTEGER);`); err != nil {
return fmt.Errorf("create _litestream_lock table: %w", err)
}
// Start a long-running read transaction to prevent other transactions
// from checkpointing.
if err := db.acquireReadLock(ctx); err != nil {
return fmt.Errorf("acquire read lock: %w", err)View on GitHub (pinned to 4ed7a308f6)
Solutions
- Ensure litestream (and the db file's directory) has write access so WAL/-shm files can be created; test `sqlite3 /path/to/db 'PRAGMA journal_mode=wal;'` manually
- Move the database to a local filesystem that supports WAL (ext4/xfs/apfs/ntfs) instead of NFS/SMB/network shares
- Verify the journal mode is persistently WAL: `sqlite3 /path/to/db 'PRAGMA journal_mode;'` — if not, set it once with the app's connections closed
- Close other connections/transactions that hold the database and restart litestream (init retries on next sync)
Example fix
-- before (on a db stuck in delete mode) PRAGMA journal_mode; -- returns 'delete' -- after PRAGMA journal_mode=wal; -- verify it returns 'wal' before starting litestream
Defensive patterns
Strategy: validation
Validate before calling
// run before starting litestream:
// sqlite3 /path/to/db 'PRAGMA journal_mode;'
// must print 'wal'. If not:
// sqlite3 /path/to/db 'PRAGMA journal_mode=wal;'
mode, err := queryScalar(db, "PRAGMA journal_mode")
if err != nil || mode != "wal" { return fmt.Errorf("db not in WAL mode: %q", mode) }
Try / catch
var mode string
if err := db.db.QueryRowContext(ctx, `PRAGMA journal_mode = wal;`).Scan(&mode); err != nil {
return err
} else if mode != "wal" {
return fmt.Errorf("enable wal failed, mode=%q", mode) // handle by fixing fs/permissions
}
Prevention
- Keep the database on a local filesystem that supports WAL, not NFS/SMB
- Ensure the db directory is writable so -wal and -shm files can be created
- Set journal_mode=wal persistently with all other connections closed
- Verify read-only mounts/flags (immutable=1 DSN flags, ro filesystems) are not in play
When it happens
Trigger: DB.init: QueryRowContext of `PRAGMA journal_mode = wal;` succeeds but Scan returns a mode string other than "wal". Classic cause: the database is opened in read-only mode or on a filesystem where WAL is unsupported (e.g. some network filesystems like older NFS), or the journal mode switch was rejected because of an active transaction on another connection.
Common situations: Placing the SQLite database on NFS/SMB/network mounts that do not support WAL; the file being opened read-only (read-only filesystem or lack of write permission on the directory needed to create -wal/-shm); another process holding the database in a mode/transaction that blocks the switch; memory-constrained or unusual VFS setups.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/6dca644e0b8e3c82.
Report an issue: GitHub.