tursodatabase/turso · error · anyhow::Error
invalid MVCC logical log frame end magic at offset {offset}
Error message
invalid MVCC logical log frame end magic at offset {offset} What it means
After the chained CRC check passes, the 8-byte trailer must end with magic 0x4554564D (MVCC_TX_END_MAGIC), read at trailer_start + 4. A CRC that matches while the end magic fails pinpoints corruption in the trailer's magic bytes themselves (the CRC does not cover the trailer), or a producer writing the two trailer fields in the wrong order. The scan stops at that frame.
Source
Thrown at cli/sync_server.rs:1096
.and_then(|value| value.checked_add(payload_size))
.and_then(|value| value.checked_add(extension_size))
.ok_or_else(|| anyhow!("MVCC logical log frame offset overflow"))?;
let frame_end = trailer_start
.checked_add(MVCC_TX_TRAILER_SIZE)
.ok_or_else(|| anyhow!("MVCC logical log frame end overflow"))?;
if frame_end > log.len() {
return Ok(None);
}
let expected_crc = crc32c::crc32c_append(running_crc, &log[offset..trailer_start]);
let stored_crc = read_u32_le(log, trailer_start)?;
if stored_crc != expected_crc {
return Err(anyhow!(
"MVCC logical log frame checksum mismatch at offset {offset}"
));
}
let end_magic = read_u32_le(log, trailer_start + 4)?;
if end_magic != MVCC_TX_END_MAGIC {
return Err(anyhow!(
"invalid MVCC logical log frame end magic at offset {offset}"
));
}
Ok(Some((frame_end, stored_crc)))
}
fn read_u32_le(buf: &[u8], offset: usize) -> Result<u32> {
let bytes = buf
.get(offset..offset + 4)
.ok_or_else(|| anyhow!("buffer too short for u32 at offset {offset}"))?;
Ok(u32::from_le_bytes(bytes.try_into().unwrap()))
}
fn read_u64_le(buf: &[u8], offset: usize) -> Result<u64> {
let bytes = buf
.get(offset..offset + 8)
.ok_or_else(|| anyhow!("buffer too short for u64 at offset {offset}"))?;
Ok(u64::from_le_bytes(bytes.try_into().unwrap()))View on GitHub (pinned to bad083fafb)
Solutions
- Re-pull the log; the trailer is corrupt even though the frame body verified.
- Hexdump the 8-byte trailer at the reported offset: crc32c u32 then 0x4554564D.
- If writing a test producer, append the trailer in the fixed order CRC then end magic.
Example fix
// before (log producer) trailer.extend_from_slice(&MVCC_TX_END_MAGIC.to_le_bytes()); // end magic first trailer.extend_from_slice(&frame_crc.to_le_bytes()); // after trailer.extend_from_slice(&frame_crc.to_le_bytes()); // CRC first trailer.extend_from_slice(&MVCC_TX_END_MAGIC.to_le_bytes());
Defensive patterns
Strategy: try-catch
Type guard
fn frame_end_magic_ok(log: &[u8], trailer_start: usize) -> bool {
log.get(trailer_start + 4..trailer_start + 8)
.map(|b| u32::from_le_bytes(b.try_into().unwrap()))
.is_some_and(|m| m == MVCC_TX_END_MAGIC)
} Try / catch
match scan_mvcc_log(&log) {
Ok(snapshot) => { /* serve deltas */ }
Err(err) if err.to_string().contains("frame end magic") => {
// trailer corrupt despite passing CRC: re-pull the log
}
Err(err) => return Err(err),
} Prevention
- In producers, write the trailer as CRC then end magic, in that fixed order.
- Write the full 8-byte trailer atomically with the frame body.
- Treat tail-of-file damage (last bytes written) as the first suspect.
When it happens
Trigger: A frame whose stored trailer CRC matches but whose second trailer u32 is not 0x4554564D — trailer magic bytes damaged after writing, or a hand-built producer that writes end-magic before CRC in the 8-byte trailer.
Common situations: Tail-of-file damage (the trailer is the last 8 bytes written); producer code swapping trailer field order; partial writes of the 8-byte trailer after a crash; fuzzed trailers.
Related errors
- invalid MVCC logical log frame magic at offset {offset}: {fr
- MVCC logical log frame checksum mismatch at offset {offset}
- invalid MVCC logical log header length: {header_len}
- MVCC logical log header reserved bytes must be zero
- MVCC logical log header checksum mismatch
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/28cf88971cd0caf9.
Report an issue: GitHub.