clockworklabs/SpacetimeDB · critical · std::io::Error
repo {}: first commit in the most recent segment is corrupt:
Error message
repo {}: first commit in the most recent segment is corrupt: {} What it means
Hard recovery failure from CommittedMeta::extract: while extracting metadata for the most recent non-empty segment, the very first commit failed to decode. Because no earlier commit in that segment survived, there is no prefix to fall back to, so the function returns InvalidData instead of a CommittedMeta::Prefix. Typically the first commit of a fresh segment was torn by a crash mid-write, or the segment's first bytes are corrupted.
Source
Thrown at crates/commitlog/src/commitlog.rs:483
// Segment is intact.
Ok(metadata) if empty_segments <= 1 => {
assert!(
!metadata.tx_range.is_empty(),
"segment was promised to be non-empty but contains zero transactions"
);
Ok(Some(CommittedMeta::Complete { metadata }))
}
// Segment is good, but there are too many empty segments.
Ok(metadata) => Ok(Some(CommittedMeta::Prefix {
metadata,
error: io::Error::new(
io::ErrorKind::InvalidData,
format!("repo {}: too many empty segments: {}", repo, empty_segments),
),
})),
// Segment is non-empty, but first commit is corrupt.
Err(error::SegmentMetadata::InvalidCommit { sofar, source }) if sofar.tx_range.is_empty() => {
Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"repo {}: first commit in the most recent segment is corrupt: {}",
repo, source
),
))
}
// Some prefix of the segment is good.
Err(error::SegmentMetadata::InvalidCommit { sofar, source }) => Ok(Some(CommittedMeta::Prefix {
metadata: sofar,
error: source,
})),
// Something went wrong, including out-of-order errors and such.
Err(error::SegmentMetadata::Io(e)) => Err(e),
}
}
}
View on GitHub (pinned to 9e0d92412f)
Solutions
- Delete (or move aside) the damaged newest segment file and let the log continue from the previous segment - commits in that segment are lost, older segments are untouched.
- If the data matters, restore the data directory from a snapshot or replica instead of dropping the segment.
- Prevent recurrence: graceful shutdowns, durable disk, no external truncation of segment files.
Defensive patterns
Strategy: fallback
Try / catch
let meta = match committed_meta(repo.clone()) {
Ok(m) => m,
Err(e) if e.kind() == io::ErrorKind::InvalidData => {
// newest segment's first commit is torn: quarantine it and retry open once
quarantine_newest_segment(&repo)?;
committed_meta(repo)?
}
Err(e) => return Err(e.into()),
}; Prevention
- Take periodic snapshots so losing the newest segment never means losing data.
- Stop nodes gracefully - the first commit of a fresh segment is the most crash-exposed write in the log.
- Never hand-edit or partially copy segment files into the data directory.
When it happens
Trigger: The node crashed while writing the first commit of a newly created segment (header written, payload or checksum missing); or the first bytes of the newest segment were corrupted on disk. Reopening the log cannot salvage that segment.
Common situations: Power loss or kill -9 right after segment rollover; bit rot on the newest segment; partially copied or restored data directories.
Related errors
- failed to read {} bytes of commit payload: {}
- failed to read checksum: {e}
- Error looking up name for truncated table {table_id:?}
- Delete for non-existent row when replaying transaction
- repo {}: too many empty segments: {}
AI-assisted analysis of clockworklabs/SpacetimeDB@9e0d92412f (2026-08-20).
Data as JSON: /api/errors/bb207cc3219e08b4.
Report an issue: GitHub.