benbjohnson/litestream · error

invalid wal header magic: %x

Error message

invalid wal header magic: %x

What it means

readHeader validates that the first 4 bytes of the WAL file are SQLite's WAL magic numbers 0x377f0682 (little-endian) or 0x377f0683 (big-endian). Any other value means the file being read is not a valid SQLite WAL, or its header is corrupt/truncated.

Source

Thrown at wal_reader.go:110

// readHeader reads the WAL header into the reader. Returns io.EOF if WAL is invalid.
func (r *WALReader) readHeader() error {
	// If we have a partial WAL, then mark WAL as done.
	hdr := make([]byte, WALHeaderSize)
	if n, err := r.r.ReadAt(hdr, 0); n < len(hdr) {
		return io.EOF
	} else if err != nil {
		return err
	}

	// Determine byte order of checksums.
	switch magic := binary.BigEndian.Uint32(hdr[0:]); magic {
	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:])

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Confirm the path passed to the reader is the '-wal' file, not the main database file
  2. Check the first 4 bytes of the file (xxd -l 4 file) — they must be 37 7f 06 82 or 37 7f 06 83
  3. If corruption is suspected, restore from the latest replica and let SQLite recreate the WAL
  4. Ensure no other process truncated or rewrote the file while it was being read
Defensive patterns

Strategy: validation

Validate before calling

hdr := make([]byte, 4)
f, _ := os.Open(walPath)
io.ReadFull(f, hdr)
magic := binary.BigEndian.Uint32(hdr)
if magic != 0x377f0682 && magic != 0x377f0683 {
    return fmt.Errorf("%s is not a SQLite WAL file (magic %x)", walPath, magic)
}

Type guard

func isWALMagic(b []byte) bool {
    m := binary.BigEndian.Uint32(b[:4])
    return m == 0x377f0682 || m == 0x377f0683
}

Try / catch

r, err := NewWALReader(ctx, f, logger)
if err != nil && strings.Contains(err.Error(), "invalid wal header magic") {
    return fmt.Errorf("%s is not a WAL file — check configured path: %w", f.Name(), err)
}

Prevention

When it happens

Trigger: Opening a file as a WAL that is actually something else (a database file, an LTX file, a log, an empty file with garbage); reading a WAL whose first bytes were overwritten by disk corruption or an interrupted header write.

Common situations: Misconfigured WAL path in config pointing at the .db file instead of the -wal file; copying/mounting volumes while SQLite was mid-checkpoint; testing the reader against synthetic or placeholder files.

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/1c6b6539204f93b2. Report an issue: GitHub.