clockworklabs/SpacetimeDB · error · std::io::Error

failed to read checksum

Error message

failed to read checksum: {e}

What it means

Raised at the last step of Commit::decode: after the record payload, every commit stores a 4-byte little-endian CRC32C checksum, and reading those bytes failed (kind preserved). Because the checksum is the final thing written for a commit, this almost always means the log ends in the middle of a commit - the header and payload landed but the checksum did not.

Solutions

  1. Rely on the node's truncation-on-recovery to drop the incomplete commit; restart the node cleanly.
  2. If it persists, truncate the segment to the last valid commit and restart.
  3. Ensure backups copy segment files only while the node is stopped, and no external process truncates the data directory.
Defensive patterns

Strategy: try-catch

Try / catch

match reader.next_commit() {
    Ok(Some(commit)) => commits.push(commit),
    Ok(None) => {}
    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
        // checksum bytes missing: torn final commit - accept the prefix and stop
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: A commit whose header and payload decoded fine but whose trailing 4 checksum bytes are missing because the write was interrupted (crash/power loss), or the segment file was externally truncated or partially copied.

Common situations: Same torn-write scenarios as the payload-read error, one step later; also seen when backups copy a segment while it is being written.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/d943a6722691a863. Report an issue: GitHub.

Appendix: source

Thrown at crates/commitlog/src/commit.rs:322

        let v = if log_format_version == 0 {
            Version::V0
        } else {
            Version::V1
        };
        let Some(hdr) = Header::decode_internal(&mut reader, v)? else {
            return Ok(None);
        };
        let mut records = vec![0; hdr.len as usize];
        reader.read_exact(&mut records).map_err(|e| {
            io::Error::new(
                e.kind(),
                format!("failed to read {} bytes of commit payload: {}", hdr.len, e),
            )
        })?;

        let chk = reader.crc32c();
        let crc = decode_u32(reader.into_inner())
            .map_err(|e| io::Error::new(e.kind(), format!("failed to read checksum: {e}")))?;

        if chk != crc {
            return Err(invalid_data(ChecksumMismatch));
        }

        Ok(Some(Self {
            min_tx_offset: hdr.min_tx_offset,
            epoch: hdr.epoch,
            n: hdr.n,
            records,
            checksum: crc,
        }))
    }

    /// Convert `self` into an iterator yielding [`Transaction`]s.
    ///
    /// The supplied [`Decoder`] is responsible for extracting individual
    /// transactions from the `records` buffer.

View on GitHub (pinned to 6dee26c6ef)