benbjohnson/litestream · error
decode ltx page: %w
Error message
decode ltx page: %w
What it means
While scanning the last LTX file page-by-page during lastPageMatch, dec.DecodePage failed with a non-EOF error, meaning the LTX payload is malformed or unreadable. EOF is normal (page not found); any other error indicates corruption of the LTX file.
Source
Thrown at db.go:1850
return false, fmt.Errorf("cannot read last synced wal page: %w", err)
}
pgno := binary.BigEndian.Uint32(frame[0:])
fsalt1 := binary.BigEndian.Uint32(frame[8:])
fsalt2 := binary.BigEndian.Uint32(frame[12:])
data := frame[WALFrameHeaderSize:]
if fsalt1 != dec.Header().WALSalt1 || fsalt2 != dec.Header().WALSalt2 {
return false, nil
}
// Verify that the last page in the WAL exists in the last LTX file.
buf := make([]byte, dec.Header().PageSize)
for {
var hdr ltx.PageHeader
if err := dec.DecodePage(&hdr, buf); errors.Is(err, io.EOF) {
return false, nil // page not found in LTX file
} else if err != nil {
return false, fmt.Errorf("decode ltx page: %w", err)
}
if pgno != hdr.Pgno {
continue // page number doesn't match
}
if !bytes.Equal(data, buf) {
continue // page data doesn't match
}
return true, nil // Page matches
}
}
// 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)View on GitHub (pinned to 4ed7a308f6)
Solutions
- `litestream reset <db>` to discard the corrupted local LTX and resnapshot
- Verify the replica copy is intact and restore from it if needed (or enable auto-recover: true)
- Check disk space and storage health at the time of the error
- Confirm litestream versions match across any manual LTX handling
Example fix
// config before
replicas:
- url: s3://bucket/db
// config after
replicas:
- url: s3://bucket/db
auto-recover: true Defensive patterns
Strategy: fallback
Validate before calling
// scan-verify the LTX body after any crash/ENOSPC
for {
var hdr ltx.PageHeader
if err := dec.DecodePage(&hdr, buf); err != nil {
if errors.Is(err, io.EOF) { break }
// corrupt: trigger reset/restore
}
} Type guard
func ltxDecodable(path string) bool {
f, err := os.Open(path); if err != nil { return false }
defer f.Close()
dec := ltx.NewDecoder(f)
return dec.DecodeHeader() == nil
} Try / catch
if err := db.Sync(ctx); err != nil {
if strings.Contains(err.Error(), "decode ltx page") {
// corrupted LTX: reset local state, resnapshot from replica
}
} Prevention
- Enable auto-recover: true so corruption triggers automatic reset
- Keep adequate free disk space to avoid truncated writes
- Validate LTX files with `litestream ltx` after incidents
When it happens
Trigger: DecodePage returns an error other than io.EOF while searching for the page number at prevWALOffset — compressed or encrypted payload corruption, truncated LTX body after a valid header.
Common situations: Partial LTX file from a crash or ENOSPC during write; storage-level bit rot; version mismatch between the LTX writer and reader.
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
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/8073e2bdf46de522.
Report an issue: GitHub.