tursodatabase/turso · error · anyhow::Error

invalid MVCC logical log frame magic at offset {offset}: {fr

Error message

invalid MVCC logical log frame magic at offset {offset}: {frame_magic:#x}

What it means

After the 56-byte header, scan_mvcc_log walks frames; each must begin with magic 0x5854564D (MVCC_TX_FRAME_MAGIC) or 0x5845564D (MVCC_TX_EXT_FRAME_MAGIC, the extension-header variant). This error fires at the exact byte offset where neither magic was found, meaning the scan reached bytes that are not a frame start. A genuinely truncated final frame returns Ok(None) instead, so this error implies real corruption or misalignment, not a clean short read.

Source

Thrown at cli/sync_server.rs:1038

        log[MVCC_LOG_HEADER_SALT_START..MVCC_LOG_HEADER_SALT_END]
            .try_into()
            .expect("fixed-size salt slice"),
    );
    Ok(crc32c::crc32c(&salt.to_le_bytes()))
}

fn read_mvcc_frame_boundary(
    log: &[u8],
    offset: usize,
    running_crc: u32,
) -> Result<Option<(usize, u32)>> {
    if log.len() - offset < MVCC_TX_HEADER_SIZE + MVCC_TX_TRAILER_SIZE {
        return Ok(None);
    }
    let frame_magic = read_u32_le(log, offset)?;
    let has_extension_header = frame_magic == MVCC_TX_EXT_FRAME_MAGIC;
    if frame_magic != MVCC_TX_FRAME_MAGIC && !has_extension_header {
        return Err(anyhow!(
            "invalid MVCC logical log frame magic at offset {offset}: {frame_magic:#x}"
        ));
    }
    let header_size = if has_extension_header {
        MVCC_TX_EXT_HEADER_SIZE
    } else {
        MVCC_TX_HEADER_SIZE
    };
    if log.len() - offset < header_size + MVCC_TX_TRAILER_SIZE {
        return Ok(None);
    }
    let payload_size = usize::try_from(read_u64_le(log, offset + 4)?)
        .map_err(|_| anyhow!("MVCC logical log payload size overflows usize"))?;
    let extension_size = if has_extension_header {
        let extension_size = usize::try_from(read_u64_le(log, offset + 24)?)
            .map_err(|_| anyhow!("MVCC logical log extension size overflows usize"))?;
        let extension_record_count = read_u32_le(log, offset + 32)?;
        let frame_flags = read_u32_le(log, offset + 36)?;

View on GitHub (pinned to bad083fafb)

Solutions

  1. Re-pull the full logical log from a consistent snapshot; a bad frame magic means frame boundaries are lost and incremental parsing cannot continue.
  2. Hexdump around the reported offset and compare with the producer's copy to see whether bytes were inserted or dropped.
  3. Ensure exactly one writer appends to the log file; coordinate or serialize writers.
  4. Confirm the log was never concatenated with another file or padded.

Example fix

// before
let snapshot = scan_mvcc_log(&log)?; // errors: invalid ... frame magic at offset N

// after: a lost boundary is unrecoverable — fall back to full bootstrap
let snapshot = match scan_mvcc_log(&log) {
    Ok(snapshot) => snapshot,
    Err(err) if err.to_string().contains("frame magic") => full_bootstrap()?,
    Err(err) => return Err(err),
};
Defensive patterns

Strategy: try-catch

Type guard

fn frame_magic_known(log: &[u8], offset: usize) -> bool {
    log.get(offset..offset + 4)
        .map(|b| u32::from_le_bytes(b.try_into().unwrap()))
        .is_some_and(|m| m == MVCC_TX_FRAME_MAGIC || m == MVCC_TX_EXT_FRAME_MAGIC)
}

Try / catch

match scan_mvcc_log(&log) {
    Ok(snapshot) => { /* serve deltas */ }
    Err(err) if err.to_string().contains("frame magic") => {
        // frame boundary lost: fall back to a full bootstrap, not a delta pull
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: scan_mvcc_log reaches an offset (just past the header or a previous frame end) whose leading u32 is neither 0x5854564D nor 0x5845564D: bytes inserted or dropped mid-log, a log concatenated with foreign data, two uncoordinated writers appending to the same file, or storage returning garbage after a crash.

Common situations: Concurrent appends to one logical log without coordination; a log truncated and re-appended from the wrong offset; concatenating logs from different generations; partially-copied files where the copy resumed at the wrong byte; fuzzed log inputs.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/92c41f0424c1650e. Report an issue: GitHub.