benbjohnson/litestream · error
new wal reader, after reset
Error message
new wal reader, after reset
What it means
After a PrevFrameMismatchError, Litestream falls back to snapshotting by re-creating the reader from the WAL header; this error means even that fallback NewWALReader failed, so the WAL header itself is unreadable. Note the message omits %w, hiding the underlying cause.
Source
Thrown at db.go:2063
if err != nil {
return result, err
}
defer walFile.Close()
walReaderLogger := db.Logger.With(LogKeySubsystem, LogSubsystemWALReader)
var rd *WALReader
if info.offset == WALHeaderSize {
if rd, err = NewWALReader(walFile, walReaderLogger); err != nil {
return result, fmt.Errorf("new wal reader: %w", err)
}
} else {
// If we cannot verify the previous frame
var pfmError *PrevFrameMismatchError
if rd, err = NewWALReaderWithOffset(ctx, walFile, info.offset, info.salt1, info.salt2, walReaderLogger); errors.As(err, &pfmError) {
db.Logger.Log(ctx, internal.LevelTrace, "prev frame mismatch, snapshotting", "err", pfmError.Err)
info.offset = WALHeaderSize
if rd, err = NewWALReader(walFile, walReaderLogger); err != nil {
return result, fmt.Errorf("new wal reader, after reset")
}
} else if err != nil {
return result, fmt.Errorf("new wal reader with offset: %w", err)
}
}
// Build a mapping of changed page numbers and their latest content.
db.setSyncDiagPhase(diagPhaseSyncPageMap,
func(s *diagState) {
s.txID = txID
s.snapshotting = info.snapshotting
s.reason = info.reason
})
if info.snapshotting {
maxSyncWALBytes = 0
}
pageMap, maxOffset, walCommit, limited, err := rd.pageMap(ctx, maxSyncWALBytes)
if err != nil {View on GitHub (pinned to 4ed7a308f6)
Solutions
- Retry the sync — a transient race with SQLite WAL restart usually resolves next iteration
- Inspect the WAL header bytes; if corrupt, force SQLite to rebuild it via PRAGMA wal_checkpoint(TRUNCATE)
- Check disk health if failures persist
- File an issue — this message drops the wrapped cause, so also consider improving it to include the underlying error
Example fix
// before
return result, fmt.Errorf("new wal reader, after reset")
// after
return result, fmt.Errorf("new wal reader, after reset: %w", err) Defensive patterns
Strategy: retry
Validate before calling
// confirm WAL header is readable before assuming transient
f, err := os.Open(dbPath + "-wal")
if err == nil {
hdr := make([]byte, 32)
n, _ := f.Read(hdr)
f.Close()
if n < 32 { /* WAL mid-restart; wait and retry */ }
} Try / catch
if err != nil && strings.Contains(err.Error(), "new wal reader, after reset") {
time.Sleep(retryInterval) // likely raced with a WAL restart
retrySync()
} Prevention
- Accept that this can race with SQLite RESTART checkpoints; retry usually fixes it
- Fix the error message in a PR to include %w for diagnosability
- Check volume error logs if it recurs
When it happens
Trigger: Prev frame mismatch triggered the offset reset to WALHeaderSize, and the subsequent NewWALReader still failed — i.e. the WAL file's header region is corrupt/truncated/empty despite the offset-based reader having read far enough to hit a mismatch.
Common situations: WAL concurrently truncated to zero by a RESTART checkpoint between the two reader constructions; disk corruption affecting the header page; race where the -wal file is replaced mid-sync.
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: %w
- 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/a8166f9f1368cf2e.
Report an issue: GitHub.