benbjohnson/litestream · error

WALReader.ReadFrame(): buffer size

Error message

WALReader.ReadFrame(): buffer size (%d) must match page size (%d)

What it means

ReadFrame/readFrame fill the caller-supplied buffer with one page per frame. The buffer must be exactly pageSize bytes (as declared in the WAL header); this error is thrown when len(data) differs from the WAL's page size, so the frame read would overflow or underfill the buffer.

Solutions

  1. Query the actual page size (PRAGMA page_size) and allocate the read buffer from it, not a constant
  2. Obtain the page size from the WALReader (r.pageSize / pageSize field) after NewWALReader* returns, then size the buffer
  3. If the page size changed mid-stream, re-open the WAL reader so the new header/page size is picked up
  4. Never reuse a buffer sized for a previous page size across readers

Example fix

// before
data := make([]byte, 4096)
n0, n1, err := reader.ReadFrame(ctx, data)
// after
data := make([]byte, reader.PageSize())
n0, n1, err := reader.ReadFrame(ctx, data)
Defensive patterns

Strategy: validation

Validate before calling

// derive the buffer from the reader, never a constant
buf := make([]byte, r.PageSize())
if len(buf) != int(r.PageSize()) { panic("page size mismatch") }

Try / catch

_, _, err := reader.ReadFrame(ctx, data)
if err != nil && strings.Contains(err.Error(), "must match page size") {
    data = make([]byte, reader.PageSize()) // re-size and retry
    _, _, err = reader.ReadFrame(ctx, data)
}

Prevention

When it happens

Trigger: Calling ReadFrame with a buffer sized from a stale or assumed page size while the WAL header declares a different one; using the default 4096-byte buffer against a database with page_size=8192 etc.

Common situations: Database page size changed via 'PRAGMA page_size' or VACUUM between replication runs; hardcoding 4096 in code that reads frames; mixing readers across databases with different page sizes.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at wal_reader.go:149

	return nil
}

// ReadFrame reads the next frame from the WAL and returns the page number.
// Returns io.EOF at the end of the valid WAL.
func (r *WALReader) ReadFrame(ctx context.Context, data []byte) (pgno, commit uint32, err error) {
	return r.readFrame(ctx, data, true)
}

func (r *WALReader) readFrame(ctx context.Context, data []byte, verifyChecksum bool) (pgno, commit uint32, err error) {
	select {
	case <-ctx.Done():
		return 0, 0, context.Cause(ctx)
	default:
	}

	if len(data) != int(r.pageSize) {
		return 0, 0, fmt.Errorf("WALReader.ReadFrame(): buffer size (%d) must match page size (%d)", len(data), r.pageSize)
	}

	frameSize := r.pageSize + WALFrameHeaderSize
	offset := WALHeaderSize + (int64(r.frameN) * int64(frameSize))

	// Read WAL frame header.
	hdr := make([]byte, WALFrameHeaderSize)
	if n, err := r.r.ReadAt(hdr, offset); n != len(hdr) {
		return 0, 0, io.EOF
	} else if err != nil {
		return 0, 0, err
	}

	// Read WAL page data.
	if n, err := r.r.ReadAt(data, offset+WALFrameHeaderSize); n != len(data) {
		return 0, 0, io.EOF
	} else if err != nil {
		return 0, 0, err

View on GitHub (pinned to 4ed7a308f6)