benbjohnson/litestream · error
prev WAL offset is less than the header size: %d
Error message
prev WAL offset is less than the header size: %d
What it means
The previously synced WAL offset stored in sync state is smaller than the 32-byte WAL header, which is structurally impossible for a valid WAL position. verify rejects it rather than reading a frame at an invalid offset.
Source
Thrown at db.go:1784
}
info.reason = "wal header salt reset, snapshotting"
return info, nil
}
// If offset is at the beginning of the first page, we can't check for previous page.
prevWALOffset := info.offset - frameSize
db.Logger.Debug("verify", "saltMatch", saltMatch, "prevWALOffset", prevWALOffset)
if prevWALOffset == WALHeaderSize {
if saltMatch { // No writes occurred since last sync, salt still matches
info.snapshotting = false
return info, nil
}
// Salt has changed but we don't know if writes occurred since last sync
info.reason = "wal header salt reset, snapshotting"
return info, nil
} else if prevWALOffset < WALHeaderSize {
return info, fmt.Errorf("prev WAL offset is less than the header size: %d", prevWALOffset)
}
// If we can't verify the last page is in the last LTX file, then we need to snapshot.
lastPageMatch, err := db.lastPageMatch(ctx, dec, prevWALOffset, frameSize)
if err != nil {
return info, fmt.Errorf("last page match: %w", err)
} else if !lastPageMatch {
info.reason = "last page does not exist in last ltx file, wal overwritten by another process"
return info, nil
}
db.Logger.Debug("verify.2", "lastPageMatch", lastPageMatch)
// Salt has changed which could indicate a FULL checkpoint.
// If we have a last page match, then we can assume that the WAL has not been overwritten.
if !saltMatch {
db.Logger.Log(ctx, internal.LevelTrace, "wal restarted",
"salt1", salt1,View on GitHub (pinned to 4ed7a308f6)
Solutions
- Run `litestream reset <db>` to discard local state and resnapshot from the replica
- Inspect the LTX file's WALHeader.WALOffset for the offending value (`litestream ltx`)
- Avoid manually editing or truncating LTX files; always restore via litestream
Defensive patterns
Strategy: fallback
Validate before calling
// sanity-check any restored LTX state
if hdr.WALOffset != 0 && hdr.WALOffset < 32 { /* invalid state: reset */ } Type guard
func validWALOffset(off int64) bool { return off == 0 || off >= 32 } Try / catch
if err := db.Sync(ctx); err != nil {
if strings.Contains(err.Error(), "less than the header size") {
// local state invalid: litestream reset and resnapshot
}
} Prevention
- Never hand-edit LTX files or offset state
- Always restore state via litestream tooling
- Run `litestream ltx` to validate files after manual recovery attempts
When it happens
Trigger: prevWALOffset (from the last sync's recorded state) is > 0 but < WALHeaderSize — corrupted or hand-edited sync state, or a zero/truncated LTX-derived offset fed into verify.
Common situations: Manually editing or partially restoring LTX/replica state; corruption of local offset bookkeeping; restoring an LTX file from an incompatible litestream version.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- decode
- last page match: %w
- cannot read last synced wal page: %w
- new wal reader with offset: %w
- write ltx from wal: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/2cda2dd7032e9e3f.
Report an issue: GitHub.