benbjohnson/litestream · error
decode header: %w
Error message
decode header: %w
What it means
ApplyLTX wraps the opened LTX reader in an ltx.Decoder and calls DecodeHeader. This error wraps a header decode failure, meaning the stream's first bytes are not a valid LTX header or could not be fully read — the file is corrupt, truncated, or not an LTX file at all.
Source
Thrown at vfs.go:852
}
return nil
}
// ApplyLTX fetches an entire LTX file and applies its pages to the hydration file.
func (h *Hydrator) ApplyLTX(ctx context.Context, info *ltx.FileInfo) error {
h.logger.Debug("applying ltx to hydration file", "level", info.Level, "min", info.MinTXID, "max", info.MaxTXID)
// Fetch entire LTX file
rc, err := h.client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, 0, 0)
if err != nil {
return fmt.Errorf("open ltx file: %w", err)
}
defer rc.Close()
dec := ltx.NewDecoder(rc)
if err := dec.DecodeHeader(); err != nil {
return fmt.Errorf("decode header: %w", err)
}
h.mu.Lock()
defer h.mu.Unlock()
// Apply each page to the hydration file
for {
var phdr ltx.PageHeader
data := make([]byte, h.pageSize)
if err := dec.DecodePage(&phdr, data); err == io.EOF {
break
} else if err != nil {
return fmt.Errorf("decode page: %w", err)
}
off := int64(phdr.Pgno-1) * int64(h.pageSize)
if _, err := h.file.WriteAt(data, off); err != nil {
return fmt.Errorf("write page %d: %w", phdr.Pgno, err)View on GitHub (pinned to 4ed7a308f6)
Solutions
- Re-download the file and check its size and first bytes against the LTX magic; a 0-byte or HTML payload points at the storage/CDN layer, not litestream.
- Compare the object's checksum with the replica listing; replace the corrupt object by re-snapshotting the database.
- Check for LTX format version skew between writer and reader; upgrade litestream if headers use a newer version.
- Skip the corrupt file and perform a full Restore from the latest valid snapshot.
Example fix
// before
dec := ltx.NewDecoder(rc)
if err := dec.DecodeHeader(); err != nil { return err }
// after
dec := ltx.NewDecoder(rc)
if err := dec.DecodeHeader(); err != nil {
if n, _ := rc.Seek(0, io.SeekEnd); n < ltx.HeaderSize {
return fmt.Errorf("ltx file truncated (%d bytes): %w", n, err)
}
return fmt.Errorf("decode header: %w", err)
} Defensive patterns
Strategy: validation
Validate before calling
hdr, err := dec.DecodeHeader()
if err != nil || hdr.Magic != ltx.Magic {
return fmt.Errorf("object is not a valid ltx file")
} Try / catch
if err := dec.DecodeHeader(); err != nil {
// verify object size; < HeaderSize means truncated upload
return fmt.Errorf("corrupt/truncated ltx header: %w", err)
} Prevention
- Verify object Content-Length matches expected size before decoding
- Avoid proxies/CDNs that may substitute HTML error bodies for objects
- Pin litestream versions so writer and reader LTX formats match
When it happens
Trigger: Calling ApplyLTX when the object downloaded from the replica is truncated (interrupted upload/download), zero bytes, encrypted/at-rest mismatch, or was overwritten by non-LTX content (e.g. wrong path written by another tool).
Common situations: Partial multipart uploads left in the bucket; a proxy/firewall returning an HTML error page stored as the object; version skew where the file was written with a newer incompatible LTX magic/version.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/5873c508df7e225c.
Report an issue: GitHub.