rqlite/rqlite · error

unsupported wal version: %d

Error message

unsupported wal version: %d

What it means

Reader.ReadHeader validates the WAL file header, including the WAL format version stored at bytes 4-8 of the 32-byte header. This error is returned when the version read from the WAL file does not equal WALSupportedVersion, meaning the library cannot safely parse this WAL format. It prevents corrupt or incompatible WAL files from being misinterpreted.

Source

Thrown at db/wal/reader.go:136

	case 0x377f0683:
		r.bo = binary.BigEndian
	default:
		return fmt.Errorf("invalid wal header magic: %x", r.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, err := WALChecksum(r.bo, 0, 0, hdr[:24]); err != nil {
		return err
	} else if v0 != chksum1 || v1 != chksum2 {
		return io.EOF
	}

	// Verify version is correct.
	if version := binary.BigEndian.Uint32(hdr[4:]); version != WALSupportedVersion {
		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 returns the next page number and commit offset from the WAL. If
// data is not nil, then the page data is read into the buffer and the checksum
// is verified. If data is nil, then the page data is skipped and no checksum
// verification is performed. Returns io.EOF at the end of the valid WAL.
func (r *Reader) ReadFrame(data []byte) (pgno, commit uint32, err error) {
	if data != nil && len(data) != int(r.pageSize) {
		return 0, 0, fmt.Errorf("WALReader.ReadFrame(): buffer size (%d) must match page size (%d)", len(data), r.pageSize)

View on GitHub (pinned to 7586a4d1bd)

Solutions

  1. Verify the WAL file is a genuine SQLite WAL whose magic header is 0x377f0682/0x377f0683 and whose version bytes match WALSupportedVersion
  2. Regenerate the WAL by checkpointing with the SQLite version that matches the supported format, then retry
  3. Check for file corruption or truncation (wrong byte offsets) and restore the WAL from backup
  4. Confirm you are reading the correct file path and not a DB or temp file

Example fix

// before: blindly scanning whatever file was passed
r, err := wal.OpenReaderAt(path)
sc, err := wal.NewFullScanner(r)
// after: check the header version first
hdr := make([]byte, 32)
f, _ := os.Open(path)
io.ReadFull(f, hdr)
if binary.BigEndian.Uint32(hdr[4:]) != wal.WALSupportedVersion {
    return fmt.Errorf("WAL version %d unsupported; regenerate WAL", binary.BigEndian.Uint32(hdr[4:]))
}
Defensive patterns

Strategy: validation

Validate before calling

func validateWALVersion(path string) error {
    f, err := os.Open(path)
    if err != nil { return err }
    defer f.Close()
    hdr := make([]byte, 32)
    if _, err := io.ReadFull(f, hdr); err != nil { return err }
    magic := binary.BigEndian.Uint32(hdr[0:4])
    if magic != 0x377f0682 && magic != 0x377f0683 {
        return fmt.Errorf("not a SQLite WAL file: magic %x", magic)
    }
    if v := binary.BigEndian.Uint32(hdr[4:8]); v != wal.WALSupportedVersion {
        return fmt.Errorf("unsupported WAL version %d (want %d)", v, wal.WALSupportedVersion)
    }
    return nil
}

Type guard

func isSupportedWAL(hdr []byte) bool {
    return len(hdr) >= 32 &&
        (binary.BigEndian.Uint32(hdr[0:4]) == 0x377f0682 ||
         binary.BigEndian.Uint32(hdr[0:4]) == 0x377f0683) &&
        binary.BigEndian.Uint32(hdr[4:8]) == wal.WALSupportedVersion
}

Try / catch

if err := validateWALVersion(walPath); err != nil {
    // quarantine and skip, or regenerate via checkpoint
    log.Printf("skipping WAL %s: %v", walPath, err)
} else if sc, err := wal.NewFullScanner(r); err != nil {
    varunsupported := strings.Contains(err.Error(), "unsupported wal version")
    if !unsupported { return err }
}

Prevention

When it happens

Trigger: Calling NewFullScanner or NewCompactingFrameScanner (both call ReadHeader) on a WAL file whose header version field differs from WALSupportedVersion. This can also occur if the WAL file is corrupted such that the version bytes are garbage, or if the file is not actually a SQLite WAL file.

Common situations: Opening a WAL file produced by a newer/older SQLite version with a different WAL format version; pointing the reader at a truncated, zero-filled, or corrupted WAL file; accidentally passing a database file or other binary instead of the -wal file.

Related errors


AI-assisted analysis of rqlite/rqlite@7586a4d1bd (2026-09-03). Data as JSON: /api/errors/a527c4ef60286cc7. Report an issue: GitHub.