benbjohnson/litestream · error
new wal reader with offset: %w
Error message
new wal reader with offset: %w
What it means
NewWALReaderWithOffset failed with an error that is not a PrevFrameMismatchError while resuming WAL reads at the last-known offset. The offset path verifies the frame preceding info.offset matches the recorded salt1/salt2; any other read/validation failure is wrapped here.
Source
Thrown at db.go:2066
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 {
return result, fmt.Errorf("page map: %w", err)
}
result.limited = limitedView on GitHub (pinned to 4ed7a308f6)
Solutions
- Run `litestream reset <dbpath>` to discard stale offsets and force a full resnapshot
- Check for duplicate Litestream processes replicating the same DB
- Verify WAL file size is >= info.offset; if truncated, a checkpoint occurred — resnapshot is expected
- Check disk/filesystem health for repeated read errors
Example fix
// before $ ls -l /data/app.db-wal # smaller than recorded offset // after $ litestream reset /data/app.db && systemctl restart litestream
Defensive patterns
Strategy: fallback
Validate before calling
st, err := os.Stat(dbPath + "-wal") usable := err == nil && st.Size() >= int64(headerSize) // if WAL smaller than saved offset, resnapshot is required
Try / catch
if err != nil && strings.Contains(err.Error(), "new wal reader with offset") {
exec.Command("litestream", "reset", dbPath).Run() // discard stale offsets
restartReplication()
} Prevention
- Don't restore/truncate the WAL outside Litestream's control
- One replicator per DB to avoid divergent offsets
- Enable `auto-recover` on replicas where unattended recovery is desired
When it happens
Trigger: Resuming a sync from info.offset where the WAL content differs in a way not classified as prev-frame mismatch: read error, short file (offset beyond EOF), invalid frame header, or context cancellation.
Common situations: WAL was checkpointed/truncated so the saved offset is beyond current EOF; another Litestream instance moved the position; on-disk corruption; salts changed without pfm classification.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- unaligned wal offset %d for page size %d
- set synchronous: %w
- checkpoint: %w
- checkpoint failed: %w
- sync database %s: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/387e3189098f35b4.
Report an issue: GitHub.