benbjohnson/litestream · error
set PERSIST_WAL: %w
Error message
set PERSIST_WAL: %w
What it means
This wraps any error produced by db.setPersistWAL during DB.init, including the connection acquisition, the FileControl type assertion, and the FileControlPersistWAL call. Litestream treats enabling PERSIST_WAL as mandatory because if SQLite deletes the WAL file when the last connection closes, replication falls out of sync. The wrapper is applied at the init call site so the failure context ('while initializing the database') is preserved.
Source
Thrown at db.go:1055
}
db.fileInfo = fi
// Obtain permissions for parent directory.
if fi, err = os.Stat(filepath.Dir(db.path)); err != nil {
return err
}
db.dirInfo = fi
dsn := fmt.Sprintf("file:%s?_pragma=busy_timeout(%d)&_pragma=wal_autocheckpoint(0)",
db.path, db.BusyTimeout.Milliseconds())
if db.db, err = sql.Open("sqlite", dsn); err != nil {
return err
}
// Set PERSIST_WAL to prevent WAL file removal when database connections close.
if err := db.setPersistWAL(ctx); err != nil {
return fmt.Errorf("set PERSIST_WAL: %w", err)
}
// Open long-running database file descriptor. Required for non-OFD locks.
if db.f, err = os.Open(db.path); err != nil {
return fmt.Errorf("open db file descriptor: %w", err)
}
// 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
}
}()
View on GitHub (pinned to 4ed7a308f6)
Solutions
- Read the innermost wrapped error to determine which sub-step failed
- Upgrade modernc.org/sqlite and rebuild if the inner error is 'driver does not implement FileControl'
- URL-escape the database path / check the path for special characters that break the file: DSN (file:%s?_pragma=...)
- Ensure the database file exists and is writable by the litestream process before startup; retry init (litestream retries on next sync)
Defensive patterns
Strategy: try-catch
Validate before calling
if _, err := os.Stat(dbPath); err != nil { return err }
if err := checkWritable(dbPath); err != nil { return err }
// verify driver capability beforehand:
// go list -m modernc.org/sqlite
Try / catch
if err := db.init(ctx); err != nil {
if strings.Contains(err.Error(), "set PERSIST_WAL") {
log.Printf("init failed at PERSIST_WAL step: %v", err)
// init retries on next sync; back off and retry
}
}
Prevention
- Ensure the litestream process has read/write access to the db file and its directory
- Pin a modernc.org/sqlite version that implements FileControl
- URL-escape special characters in the database path used in the file: DSN
- Test litestream startup against a copy of the production database in CI
When it happens
Trigger: Calling DB.init (triggered by opening a database with litestream.Open or the first sync): sql.Open succeeds, then db.setPersistWAL(ctx) returns any error — get connection failure, 'driver does not implement FileControl', or 'FileControlPersistWAL: ...' — which is then re-wrapped as 'set PERSIST_WAL: %w'.
Common situations: App startup with an incompatible modernc.org/sqlite version (assertion failure); the SQLite database file disappearing or being locked between sql.Open and the connection; driver errors from an odd DSN built from the db path (spaces/special characters not URL-escaped in file: DSN).
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
- local position: %w
- FileControlPersistWAL: %w
- create _litestream_seq table: %w
- create _litestream_lock table: %w
- acquire read lock: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/acfc9222adcc2baa.
Report an issue: GitHub.