benbjohnson/litestream · error
new wal reader: %w
Error message
new wal reader: %w
What it means
NewWALReader failed to parse the WAL header when detectFullCheckpoint validates the WAL. Litestream requires a valid 32-byte WAL header (magic, format version, page size, checkpoint sequence); a malformed or empty header makes the reader unusable.
Source
Thrown at db.go:1879
}
// detectFullCheckpoint attempts to detect checks if a FULL or RESTART checkpoint
// has occurred and we may have missed some frames.
func (db *DB) detectFullCheckpoint(ctx context.Context, knownSalts [][2]uint32) (bool, error) {
walFile, err := os.Open(db.WALPath())
if err != nil {
return false, fmt.Errorf("open wal file: %w", err)
}
defer walFile.Close()
var lastKnownSalt [2]uint32
if len(knownSalts) > 0 {
lastKnownSalt = knownSalts[len(knownSalts)-1]
}
rd, err := NewWALReader(walFile, db.Logger.With(LogKeySubsystem, LogSubsystemWALReader))
if err != nil {
return false, fmt.Errorf("new wal reader: %w", err)
}
m, err := rd.FrameSaltsUntil(ctx, lastKnownSalt)
if err != nil {
return false, fmt.Errorf("frame salts until: %w", err)
}
// Remove known salts from the map.
for _, salt := range knownSalts {
delete(m, salt)
}
// If we have more than one unknown salt, then we have a FULL or RESTART checkpoint.
return len(m) >= 1, nil
}
type syncInfo struct {
offset int64 // end of the previous LTX read
salt1 uint32View on GitHub (pinned to 4ed7a308f6)
Solutions
- Inspect the first 32 bytes of the -wal file to confirm valid SQLite WAL magic
- Trigger a checkpoint (PRAGMA wal_checkpoint(TRUNCATE)) or restart the app so SQLite rewrites a fresh WAL
- Restore the WAL file from backup if it is corrupt
- Verify the disk is not full / filesystem not corrupt
Example fix
// before $ xxd /data/app.db-wal | head -1 00000000: 0000 0000 0000 0000 ... (zeroed header) // after $ sqlite3 /data/app.db 'PRAGMA wal_checkpoint(TRUNCATE);' # regenerates a valid WAL
Defensive patterns
Strategy: validation
Validate before calling
hdr, err := os.ReadFile(walPath) valid := err == nil && len(hdr) >= 32 && (binary.BigEndian.Uint32(hdr[0:4]) == 0x377f0682 || binary.BigEndian.Uint32(hdr[0:4]) == 0x377f0683)
Try / catch
if err != nil {
if strings.Contains(err.Error(), "new wal reader") {
// WAL header corrupt: checkpoint via the app, then restart litestream
run("sqlite3 "+dbPath+" 'PRAGMA wal_checkpoint(TRUNCATE);'")
}
} Prevention
- Protect the DB volume from power loss (fsync-capable storage) to avoid zeroed WAL headers
- Never edit or truncate -wal files manually
- Use `litestream reset` after restoring from inconsistent snapshots
- Keep SQLite and Litestream versions current
When it happens
Trigger: NewWALReader(walFile, logger) returns an error: WAL file smaller than WALHeaderSize, invalid WAL magic bytes (not 0x377f0682/0x377f0683), or unsupported page size in the header.
Common situations: A non-SQLite or corrupted file at the WAL path; WAL truncated mid-header by a crash or disk-full condition; an SQLite version/pragma producing an unsupported WAL header; file zeroed by storage failure.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- new wal reader, after reset
- invalid wal header magic: %x
- set synchronous: %w
- checkpoint: %w
- checkpoint failed: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/baca5922daadfa4f.
Report an issue: GitHub.