tursodatabase/turso · error

invalid MVCC logical log header flags

Error message

invalid MVCC logical log header flags

What it means

Byte 5 of the MVCC logical log header is a flags field where only bit 0 (0x1) is defined; validate_mvcc_log_header requires (log[5] & 0b1111_1110) == 0. The error fires when any other bit is set, which indicates either a corrupted header or a future format revision that assigned new header flag bits this build does not understand.

Solutions

  1. Restore the MVCC logical log from a known-good backup (single flipped bit or torn write is the usual cause)
  2. Delete/regenerate the log and let clients resync via replace-base fallback
  3. If you maintain the writer, verify no build sets header flags beyond bit 0 before shipping a format change
Defensive patterns

Strategy: validation

Validate before calling

fn mvcc_header_flags_ok(log: &[u8]) -> bool {
    log.len() > 5 && (log[5] & 0b1111_1110) == 0
}

Type guard

fn is_likely_valid_mvcc_header(log: &[u8]) -> bool {
    log.len() >= MVCC_LOG_HEADER_SIZE
        && read_u32_le(log, 0) == Ok(MVCC_LOG_MAGIC)
        && log[4] == MVCC_LOG_VERSION
        && (log[5] & 0b1111_1110) == 0
}

Try / catch

if let Err(err) = scan_mvcc_log(&log) {
    if err.to_string().contains("MVCC logical log header") {
        // header-level corruption: quarantine file, alert, fall back to replace-base
    }
}

Prevention

When it happens

Trigger: scan_mvcc_log -> validate_mvcc_log_header on a >= 56-byte log whose flags byte at log[5] has any bit above bit 0 set (e.g. 0x02, 0x80) during a POST /pull-updates logical pull.

Common situations: Bit rot or a torn header write after a crash during log creation; running a fork that writes extra header flags against a stock tursodb server; a log file edited or truncated then re-padded manually.

Related errors


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

Appendix: source

Thrown at cli/sync_server.rs:1106

        end_offset: offset as u64,
        crc_by_offset,
    })
}

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]);

View on GitHub (pinned to 492c4a71cd)