{"record":{"id":"89e307cee8036acf","repo":"tursodatabase/turso","slug":"truncated-mvcc-logical-log-header-len-header-s","errorCode":null,"errorMessage":"truncated MVCC logical log header: len={} header_size={}","messagePattern":"truncated MVCC logical log header: len=(.+?) header_size=(.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"cli/sync_server.rs","lineNumber":951,"sourceCode":"        return offset\n            .parse::<u64>()\n            .map_err(|err| anyhow!(\"invalid MVCC pull revision offset: {revision}: {err}\"));\n    }\n    // Older page bootstrap responses from this test server used WAL frame\n    // numbers. Treat them as \"the page snapshot already includes the current\n    // logical log\" so the required follow-up logical pull becomes a no-op.\n    Ok(legacy_default)\n}\n\nfn scan_mvcc_log(log: &[u8]) -> Result<MvccLogSnapshot> {\n    if log.is_empty() {\n        return Ok(MvccLogSnapshot {\n            end_offset: 0,\n            crc_by_offset: vec![(0, 0)],\n        });\n    }\n    if log.len() < MVCC_LOG_HEADER_SIZE {\n        return Err(anyhow!(\n            \"truncated MVCC logical log header: len={} header_size={}\",\n            log.len(),\n            MVCC_LOG_HEADER_SIZE\n        ));\n    }\n    validate_mvcc_log_header(log)?;\n    let mut running_crc = initial_mvcc_log_crc(log)?;\n    let mut offset = MVCC_LOG_HEADER_SIZE;\n    let mut crc_by_offset = vec![(MVCC_LOG_HEADER_SIZE as u64, running_crc)];\n\n    while offset < log.len() {\n        let Some((frame_end, frame_crc)) = read_mvcc_frame_boundary(log, offset, running_crc)?\n        else {\n            break;\n        };\n        running_crc = frame_crc;\n        offset = frame_end;\n        crc_by_offset.push((offset as u64, running_crc));","sourceCodeStart":933,"sourceCodeEnd":969,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/cli/sync_server.rs#L933-L969","documentation":"scan_mvcc_log requires at least MVCC_LOG_HEADER_SIZE (56) bytes before validating anything. A .db-log that exists but is 1-55 bytes cannot hold a valid lml3 header and fails with HTTP 500. A zero-byte file is special-cased as an empty log, and a missing file triggers the replace-base fallback, so only short-but-nonempty files land here.","triggerScenarios":"A crash-truncated first write of the log; a stray or manually created small file at the .db-log path; an incompatible writer that produced only a few bytes.","commonSituations":"Killing the process during log creation; leftover files from a different tool sharing the .db-log name; partially synced/copied file pairs.","solutions":["Move or delete the short .db-log — the server then takes the NotFound path and answers with replace-base pages so clients resync.","Treat .db and .db-log as an atomic pair: recreate both together.","If truncation recurs after crashes, investigate MVCC log write durability (the header should be written atomically or last)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if let Ok(meta) = std::fs::metadata(&log_path) {\n    let len = meta.len() as usize;\n    if len > 0 && len < 56 {\n        // short-but-nonempty log will be rejected; remove it so the server\n        // falls back to replace-base pages\n        std::fs::remove_file(&log_path)?;\n    }\n}","typeGuard":null,"tryCatchPattern":"On 500 'truncated MVCC logical log header', move the .db-log aside (server then serves replace-base pages), let clients re-bootstrap, and investigate why the write was interrupted.","preventionTips":["Keep .db and .db-log as a pair; recreate them together.","After crashes, check .db-log size before resuming logical pulls.","Investigate recurring truncation as an MVCC log durability bug."],"tags":["mvcc","logical-log","truncation","file-format","corruption"],"backgroundTag":"truncated-log-file","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}