benbjohnson/litestream · error

decode page: %w

Error message

decode page: %w

What it means

ApplyLTX reads each page of the LTX file with dec.DecodePage into a pageSize buffer; io.EOF ends the loop, any other error becomes this wrapper. It means a page record inside the LTX stream is malformed, the stream is truncated mid-page, or the checksum failed — the file cannot be applied incrementally.

Source

Thrown at vfs.go:865

	}
	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)
		}
	}

	return nil
}

// ReadAt reads data from the hydrated local file.
func (h *Hydrator) ReadAt(p []byte, off int64) (int, error) {
	h.mu.Lock()
	n, err := h.file.ReadAt(p, off)
	h.mu.Unlock()

	if err != nil && err != io.EOF {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Verify page size consistency: the LTX header's page size must equal h.pageSize; re-create the hydration file with the matching page size.
  2. Re-download and checksum the LTX file; replace corrupt replica objects by forcing a new snapshot.
  3. Fall back to a full Restore (Hydrator.Restore) instead of incremental ApplyLTX when any file fails to decode.
  4. Check the storage layer for truncated objects (Content-Length vs actual bytes) and fix the upload path.

Example fix

// before
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) }
// after
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 (pageSize=%d, header pageSize=%d)", err, h.pageSize, dec.Header().PageSize)
}
Defensive patterns

Strategy: validation

Validate before calling

if dec.Header().PageSize != h.pageSize {
    return fmt.Errorf("page size mismatch: ltx=%d hydrator=%d", dec.Header().PageSize, h.pageSize)
}

Try / catch

if err := dec.DecodePage(&phdr, data); err != nil && err != io.EOF {
    return fmt.Errorf("corrupt ltx page %d: %w", phdr.Pgno, err)
}

Prevention

When it happens

Trigger: Calling ApplyLTX on a corrupt or truncated LTX file, a page whose size doesn't match h.pageSize (page-size mismatch between writer and hydration file), or stream corruption surfaced mid-decode.

Common situations: Interrupted uploads producing truncated LTX objects; databases backed up with a different -page-size than the hydration file was created with; bit-rot or CDN-transferred corruption.

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/5d96eb825a80900c. Report an issue: GitHub.