benbjohnson/litestream · critical · ErrLTXCorrupted
verify
Error message
verify
What it means
During restore/apply, each LTX file is verified with the LTX decoder before applying. If dec.Verify() fails, the error is wrapped by NewLTXError with op "verify" and tagged ErrLTXCorrupted. It means the local LTX file failed checksum/structural verification and cannot be trusted.
Source
Thrown at db.go:634
}
minTXID, maxTXID, err := db.MaxLTX()
if err != nil {
return ltx.Pos{}, err
} else if minTXID == 0 {
return ltx.Pos{}, nil // no replication yet
}
ltxPath := db.LTXPath(0, minTXID, maxTXID)
f, err := os.Open(ltxPath)
if err != nil {
return ltx.Pos{}, NewLTXError("open", ltxPath, 0, uint64(minTXID), uint64(maxTXID), err)
}
defer func() { _ = f.Close() }()
dec := ltx.NewDecoder(f)
if err := dec.Verify(); err != nil {
return ltx.Pos{}, NewLTXError("verify", ltxPath, 0, uint64(minTXID), uint64(maxTXID), fmt.Errorf("%w: %w", ErrLTXCorrupted, err))
}
pos := dec.PostApplyPos()
db.pos.value = &pos
return pos, nil
}
// invalidatePosCache clears the cached position so the next call to Pos()
// recomputes it from disk. Call this when L0 LTX files are deleted or
// when the L0 directory is cleared.
func (db *DB) invalidatePosCache() {
db.pos.Lock()
db.pos.value = nil
db.pos.Unlock()
}
// Notify returns a channel that closes when the shadow WAL changes.View on GitHub (pinned to 4ed7a308f6)
Solutions
- Run litestream reset on the database to clear corrupted local LTX state, then re-replicate
- Verify remote replicas still hold intact files and restore from a replica instead of local cache
- Check host disk health (SMART) and free space; fix the corruption source
- If remote files are also corrupt, restore from the latest valid backup point before the corruption
- Enable auto-recover replica option to automatically reset local state on LTX errors
Example fix
// before: restore keeps failing on corrupt local state $ litestream restore -o db.sqlite /path/to/db // verify: ltx corrupted // after: clear local state and retry $ litestream reset /path/to/db $ litestream restore -o db.sqlite /path/to/db
Defensive patterns
Strategy: fallback
Validate before calling
// Verify LTX integrity outside the apply path before restore
dec := ltx.NewDecoder(f)
if err := dec.Verify(); err != nil {
return fmt.Errorf("%w: %w", ErrLTXCorrupted, err)
} Try / catch
pos, err := db.restoreApplyLTX(ltxPath, minTXID, maxTXID)
if errors.Is(err, ErrLTXCorrupted) {
// fall back: litestream reset + restore from remote replica
} Prevention
- Monitor disk health and free space on the replication host
- Never manually copy or rsync LTX directories mid-write
- Enable the auto-recover replica option for automatic reset on corruption
- Keep at least one healthy remote replica for fallback restore
When it happens
Trigger: Applying or restoring from an LTX file whose bytes were corrupted — truncated download, bit rot on disk, partial write during crash, or tampering; dec.Verify() returns non-nil and db.go wraps it as op=verify with minTXID/maxTXID context.
Common situations: Disk full during replication producing truncated files; faulty storage medium; interrupted network copy of the database directory; manually copied/partially synced LTX directories between hosts.
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
- invalid replica, checksum mismatch
- decode page: %w
- peek header: %w
- decode database: %w
- ltx file corrupted
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/af965a49ab39bd3c.
Report an issue: GitHub.