benbjohnson/litestream · error

decode page: %w

Error message

decode page: %w

What it means

This error is returned by Replica.applyLTXFile while streaming pages out of an LTX transaction file during replication/restore. The underlying ltx.Decoder.DecodePage failed, meaning the page stream is corrupt, truncated, or the header/pageSize does not match what was encoded. Litestream treats it as a fatal LTX integrity failure because a partial page application would corrupt the database.

Source

Thrown at replica.go:971

	dec := ltx.NewDecoder(rc)
	if err := dec.DecodeHeader(); err != nil {
		return fmt.Errorf("decode header: %w", err)
	}

	hdr := dec.Header()

	if err := internal.LockFileExclusive(f); err != nil {
		return fmt.Errorf("acquire exclusive lock: %w", err)
	}
	defer internal.UnlockFile(f)

	for {
		var phdr ltx.PageHeader
		data := make([]byte, pageSize)
		if err := dec.DecodePage(&phdr, data); err == io.EOF {
			break
		} else if err != nil {
			return fmt.Errorf("decode page: %w", err)
		}

		if phdr.Pgno == 1 && len(data) >= 28 {
			data[18], data[19] = 0x01, 0x01
			_, _ = rand.Read(data[24:28])
		}

		off := int64(phdr.Pgno-1) * int64(pageSize)
		if _, err := f.WriteAt(data, off); err != nil {
			return fmt.Errorf("write page %d: %w", phdr.Pgno, err)
		}
	}

	if hdr.Commit > 0 {
		if err := f.Sync(); err != nil {
			return fmt.Errorf("sync before truncate: %w", err)
		}
		newSize := int64(hdr.Commit) * int64(pageSize)

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Run `litestream reset <db>` to discard corrupted local LTX state and re-sync from the replica
  2. Delete or quarantine the corrupted LTX object in remote storage so the replica re-uploads a valid one
  3. Verify network stability between the host and the storage backend (retries/proxy timeouts)
  4. Confirm the LTX file was produced by a compatible Litestream/LTX version; re-create the replica with a matching version

Example fix

// before: silently skipping or retrying a corrupt file
currentTXID = info.MaxTXID
// after: surface the error and reset state
if err := r.applyLTXFile(ctx, f, info, pageSize); err != nil {
    return fmt.Errorf("decode page: %w", err) // then run: litestream reset <db>
}
Defensive patterns

Strategy: try-catch

Validate before calling

func ltxLooksIntact(info *ltx.FileInfo, rc io.Reader) error {
    dec := ltx.NewDecoder(rc)
    if err := dec.DecodeHeader(); err != nil { return err }
    buf := make([]byte, dec.Header().PageSize)
    for {
        var ph ltx.PageHeader
        if err := dec.DecodePage(&ph, buf); err == io.EOF { break } else if err != nil { return err }
    }
    return dec.Close()
}

Try / catch

err := r.applyLTXFile(ctx, f, info, pageSize)
var ltxErr *ltx.Error
if errors.As(err, &ltxErr) || strings.Contains(err.Error(), "decode page") {
    // corrupted stream: reset local state and re-sync
    exec.Command("litestream", "reset", dbPath).Run()
    return fmt.Errorf("ltx stream corrupt, state reset: %w", err)
}
return err

Prevention

When it happens

Trigger: dec.DecodePage returns a non-EOF error inside the page loop: corrupted/truncated LTX file in storage, a remote replica stream interrupted mid-read, an LTX file written by an incompatible LTX format version, or a pageSize mismatch between the DB and the LTX header.

Common situations: Interrupted uploads leaving partial LTX objects in S3/GCS, manual edits or re-compression of LTX files, restoring across Litestream versions with differing LTX formats, network disconnects during replica sync/follow mode, storage lifecycle policies truncating objects.

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/34fd883c81b0a6e7. Report an issue: GitHub.