tursodatabase/turso · error

invalid MVCC logical log header length

Error message

invalid MVCC logical log header length: {header_len}

What it means

Bytes 6..8 of the MVCC logical log header hold a little-endian u16 header length that must equal MVCC_LOG_HEADER_SIZE (56). The check exists so the parser can locate the salt and CRC fields reliably; any other value means the header layout is not the one this build knows how to read.

Solutions

  1. Restore the log from backup or regenerate it - a mismatched header length is not recoverable in place
  2. Match the tursodb version that produced the file if the log came from another build
  3. If forking the format, keep header_len in sync with MVCC_LOG_HEADER_SIZE and bump MVCC_LOG_VERSION together
Defensive patterns

Strategy: validation

Validate before calling

fn mvcc_header_length_ok(log: &[u8]) -> bool {
    log.len() >= 8
        && u16::from_le_bytes([log[6], log[7]]) as usize == MVCC_LOG_HEADER_SIZE // 56
}

Try / catch

let snapshot = match scan_mvcc_log(&log) {
    Ok(s) => s,
    Err(err) if err.to_string().starts_with("invalid MVCC logical log header length") => {
        // layout mismatch: regenerate log, do not attempt partial reads
        return rebuild_log_and_fallback();
    }
    Err(err) => return Err(err),
};

Prevention

When it happens

Trigger: validate_mvcc_log_header computes u16::from_le_bytes([log[6], log[7]]) and finds a value != 56 while serving a logical /pull-updates request.

Common situations: A future format that grew the header; a header written in big-endian by mistake in a fork; garbage bytes from corruption or a partially written header after a crash during log creation.

Related errors


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20). Data as JSON: /api/errors/fbc1bb1637e1c611. Report an issue: GitHub.

Appendix: source

Thrown at cli/sync_server.rs:1111

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 492c4a71cd)