tursodatabase/turso · error
invalid MVCC logical log header length: {header_len}
Error message
invalid MVCC logical log header length: {header_len} What it means
The MVCC logical log begins with a fixed 56-byte header (MVCC_LOG_HEADER_SIZE). Bytes 6-8 hold a little-endian u16 header length that must equal 56; validate_mvcc_log_header throws when any other value is present. The magic (0x4C4D4C32), version (3), and flags checks run first, so reaching this error means the file claims to be a current MVCC log but encodes a different header layout, and the scanner refuses to misread field offsets.
Source
Thrown at cli/sync_server.rs:995
fn is_nonportable_mvcc_log_error(err: &anyhow::Error) -> bool {
let message = err.to_string();
message.starts_with("unsupported MVCC logical log version ")
}
fn validate_mvcc_log_header(log: &[u8]) -> Result<()> {
if read_u32_le(log, 0)? != MVCC_LOG_MAGIC {
return Err(anyhow!("invalid MVCC logical log magic"));
}
if log[4] != MVCC_LOG_VERSION {
return Err(anyhow!("unsupported MVCC logical log version {}", log[4]));
}
if log[5] & 0b1111_1110 != 0 {
return Err(anyhow!("invalid MVCC logical log header flags"));
}
let header_len = u16::from_le_bytes([log[6], log[7]]) as usize;
if header_len != MVCC_LOG_HEADER_SIZE {
return Err(anyhow!(
"invalid MVCC logical log header length: {header_len}"
));
}
if log[MVCC_LOG_HEADER_RESERVED_START..MVCC_LOG_HEADER_CRC_START]
.iter()
.any(|byte| *byte != 0)
{
return Err(anyhow!(
"MVCC logical log header reserved bytes must be zero"
));
}
let stored_crc = read_u32_le(log, MVCC_LOG_HEADER_CRC_START)?;
let mut crc_buf = [0u8; MVCC_LOG_HEADER_SIZE];
crc_buf.copy_from_slice(&log[..MVCC_LOG_HEADER_SIZE]);
crc_buf[MVCC_LOG_HEADER_CRC_START..MVCC_LOG_HEADER_SIZE].fill(0);
let expected_crc = crc32c::crc32c(&crc_buf);
if stored_crc != expected_crc {
return Err(anyhow!("MVCC logical log header checksum mismatch"));View on GitHub (pinned to bad083fafb)
Solutions
- Re-pull or regenerate the MVCC logical log from the producer; a header-length mismatch means the local copy cannot be parsed safely.
- Hexdump the first 56 bytes (xxd -l 56 <log>) and verify the magic 0x4C4D4C32 at offset 0 and the u16 at offsets 6-8 equals 56 (0x38).
- Confirm writer and reader run the same engine build (56-byte header, MVCC_LOG_VERSION 3) and upgrade the older side.
- Check the file was not mixed up or partially copied: compare size and hash against the producer's log.
Example fix
// before
let snapshot = scan_mvcc_log(&log)?; // errors: invalid MVCC logical log header length
// after: verify the header layout before scanning, then re-pull on mismatch
fn mvcc_header_length_ok(log: &[u8]) -> bool {
log.len() >= MVCC_LOG_HEADER_SIZE
&& u16::from_le_bytes([log[6], log[7]]) as usize == MVCC_LOG_HEADER_SIZE
}
if !mvcc_header_length_ok(&log) {
std::fs::remove_file(&log_path)?;
return rebootstrap_log_from_server();
}
let snapshot = scan_mvcc_log(&log)?; Defensive patterns
Strategy: validation
Validate before calling
const MVCC_LOG_HEADER_SIZE: usize = 56;
fn mvcc_header_length_ok(log: &[u8]) -> bool {
log.len() >= MVCC_LOG_HEADER_SIZE
&& u16::from_le_bytes([log[6], log[7]]) as usize == MVCC_LOG_HEADER_SIZE
}
// run before scan_mvcc_log:
if !mvcc_header_length_ok(&log) {
anyhow::bail!("log header length is not {MVCC_LOG_HEADER_SIZE}; re-pull the log");
} Type guard
fn mvcc_header_length_ok(log: &[u8]) -> bool {
log.len() >= MVCC_LOG_HEADER_SIZE
&& u16::from_le_bytes([log[6], log[7]]) as usize == MVCC_LOG_HEADER_SIZE
} Try / catch
match scan_mvcc_log(&log) {
Ok(snapshot) => { /* serve deltas from crc_by_offset */ }
Err(err) if err.to_string().contains("invalid MVCC logical log header length") => {
// deterministic format error: re-bootstrap, never retry the same bytes
}
Err(err) => return Err(err),
} Prevention
- Pin writer and reader to the same tursodb build when using MVCC sync.
- Verify log copies with a hash (sha256) before pointing the sync server at them.
- Never hand-edit, pad, or concatenate MVCC logical log files.
- Zero the full 56-byte header before filling fields when building test logs.
When it happens
Trigger: scan_mvcc_log(&log) on a log whose bytes 6-8 decode to anything other than 56: a log written by an experimental or divergent build with a changed header size, a header corrupted after writing, or a hand-built test fixture that filled the length field incorrectly.
Common situations: Sync client and server built from different Turso revisions during a rolling upgrade; experimental MVCC format iterations that kept version=3 but changed header layout; the wrong file copied over the logical log path; truncated-then-repaired copies that garbled the middle of the header.
Related errors
- MVCC logical log header reserved bytes must be zero
- MVCC logical log extension record count without extension bl
- MVCC logical log extension block missing flag at offset {off
- MVCC logical log header checksum mismatch
- invalid MVCC logical log frame magic at offset {offset}: {fr
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/fbc1bb1637e1c611.
Report an issue: GitHub.