benbjohnson/litestream · error

unsupported wal version: %d

Error message

unsupported wal version: %d

What it means

SQLite embeds a format version (3007000, i.e. SQLite 3.7.0) at bytes 4-8 of the WAL header. Litestream rejects WAL files written with any other version, as their frame format cannot be assumed compatible. Note this check only runs after the header checksum passes, so the version field is trusted.

Source

Thrown at wal_reader.go:123

	case 0x377f0682:
		r.bo = binary.LittleEndian
	case 0x377f0683:
		r.bo = binary.BigEndian
	default:
		return fmt.Errorf("invalid wal header magic: %x", magic)
	}

	// If the header checksum doesn't match then we may have failed with
	// a partial WAL header write during checkpointing.
	chksum1 := binary.BigEndian.Uint32(hdr[24:])
	chksum2 := binary.BigEndian.Uint32(hdr[28:])
	if v0, v1 := WALChecksum(r.bo, 0, 0, hdr[:24]); v0 != chksum1 || v1 != chksum2 {
		return io.EOF
	}

	// Verify version is correct.
	if version := binary.BigEndian.Uint32(hdr[4:]); version != 3007000 {
		return fmt.Errorf("unsupported wal version: %d", version)
	}

	r.pageSize = binary.BigEndian.Uint32(hdr[8:])
	r.seq = binary.BigEndian.Uint32(hdr[12:])
	r.salt1 = binary.BigEndian.Uint32(hdr[16:])
	r.salt2 = binary.BigEndian.Uint32(hdr[20:])
	r.chksum1, r.chksum2 = chksum1, chksum2

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

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Verify the SQLite version writing the WAL — standard releases use version 3007000 and should never trigger this
  2. Hexdump the header (xxd -l 32 file) and inspect bytes 4-8 for the expected 00 2d e2 18 (3007000)
  3. If a custom SQLite build is in use, rebuild against stock SQLite
  4. Restore from replica and re-replicate if the WAL is corrupt
Defensive patterns

Strategy: try-catch

Validate before calling

hdr := make([]byte, 8)
io.ReadFull(f, hdr)
if binary.BigEndian.Uint32(hdr[4:]) != 3007000 {
    return fmt.Errorf("unsupported WAL version %d; need stock SQLite WAL format", binary.BigEndian.Uint32(hdr[4:]))
}

Try / catch

r, err := NewWALReader(ctx, f, logger)
if err != nil && strings.Contains(err.Error(), "unsupported wal version") {
    return fmt.Errorf("WAL written by non-standard SQLite build: %w", err)
}

Prevention

When it happens

Trigger: Reading a WAL file whose version field is not exactly 3007000 — practically only possible with a future/hypothetical SQLite WAL format change, a manually crafted file, or corruption that happens to pass the checksum.

Common situations: Extremely rare; mostly seen when a non-SQLite tool wrote the file or a hand-built SQLite fork changed the WAL format; could also appear when pointing Litestream at an unrelated file that coincidentally has valid magic and checksum.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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