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

  1. `litestream reset <db>` to discard the corrupted local LTX and resnapshot
  2. Verify the replica copy is intact and restore from it if needed (or enable auto-recover: true)
  3. Check disk space and storage health at the time of the error
  4. 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

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.