benbjohnson/litestream · error

encode ltx frame (pgno=%d): %w

Error message

encode ltx frame (pgno=%d): %w

What it means

The LTX encoder rejected a page encode during the DB+WAL snapshot write. Litestream wraps the encoder error as 'encode ltx frame (pgno=%d):' identifying the offending page. The ltx library enforces invariants such as strictly increasing page numbers and valid page sizes, so this almost always indicates corrupted internal state rather than caller error.

Source

Thrown at db.go:2315

		// Check if the caller has canceled during processing.
		select {
		case <-ctx.Done():
			return context.Cause(ctx)
		default:
		}

		// If page exists in the WAL, read from there.
		if offset, ok := pageMap[pgno]; ok {
			db.Logger.Log(ctx, internal.LevelTrace, "encode page from wal", "txid", enc.Header().MinTXID, "offset", offset, "pgno", pgno, "type", "db+wal")

			if n, err := walFile.ReadAt(data, offset+WALFrameHeaderSize); err != nil {
				return fmt.Errorf("read page %d @ %d: %w", pgno, offset, err)
			} else if n != len(data) {
				return fmt.Errorf("short read page %d @ %d", pgno, offset)
			}

			if err := enc.EncodePage(ltx.PageHeader{Pgno: pgno}, data); err != nil {
				return fmt.Errorf("encode ltx frame (pgno=%d): %w", pgno, err)
			}
			continue
		}

		offset := int64(pgno-1) * int64(db.pageSize)
		db.Logger.Log(ctx, internal.LevelTrace, "encode page from database", "offset", offset, "pgno", pgno)

		// Otherwise read directly from the database file.
		if _, err := db.f.ReadAt(data, offset); err != nil {
			return fmt.Errorf("read database page %d: %w", pgno, err)
		}
		if err := enc.EncodePage(ltx.PageHeader{Pgno: pgno}, data); err != nil {
			return fmt.Errorf("encode ltx frame (pgno=%d): %w", pgno, err)
		}
	}

	return nil
}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Verify the configured page size matches the actual SQLite page size (`PRAGMA page_size;`).
  2. Run `litestream reset` for the database to clear local LTX state and start a fresh snapshot.
  3. Upgrade litestream and the ltx library to matching versions; LTX files are immutable so old incompatible state must be reset.
  4. If reproducible, capture the pgno and report with the full error chain, since out-of-order pages indicate internal state corruption.

Example fix

// before
# db recreated with page_size=8192 but config assumes 4096
# litestream.yml
# (no page size handling) -> encode errors on snapshot
// after
sqlite3 db "PRAGMA page_size;"   # confirm actual size
litestream reset /path/to/db      # clear stale LTX state
Defensive patterns

Strategy: validation

Validate before calling

const pageSize = db.pragma('page_size') // confirm matches litestream's expected size
if (pageSize !== expectedPageSize) throw new Error('page size mismatch; reset litestream state')

Try / catch

if err := db.Sync(ctx); err != nil {
	if strings.Contains(err.Error(), "encode ltx frame") {
		// capture pgno from message; reset LTX state
	}
	return err
}

Prevention

When it happens

Trigger: enc.EncodePage(ltx.PageHeader{Pgno: pgno}, data) at db.go:2314/2327 failed: pages emitted out of pgno order, a bad pageSize configured on the encoder (must match the SQLite page size), encoder closed or in an invalid state, or header/page mismatch after partial writes.

Common situations: Mismatched page size between the database file and litestream configuration after the DB was recreated with a different page_size; interrupted prior sync leaving an encoder in a bad state; bugs from manually editing/copying LTX state; ltx library version incompatibility with existing LTX files.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/4d9e6455c1f6fb2d. Report an issue: GitHub.