clockworklabs/SpacetimeDB · critical · std::io::Error
no valid commits in segment
Error message
no valid commits in segment
What it means
While resuming an existing commitlog (repo/mod.rs), a segment file longer than segment::Header::LEN is traversed to build its Metadata. If the traversed tx_range ends up empty, the file has bytes beyond the header but not a single usable commit, and resume fails with io::ErrorKind::InvalidData 'no valid commits in segment'.
Source
Thrown at crates/commitlog/src/repo/mod.rs:340
.open_segment_reader(offset)
.map_err(|source| with_segment_context("opening segment for resume", repo, offset, source))?;
// If the segment at `offset` is empty, remove it and try the previous.
// Return an error if no previous segment is found.
let len = reader
.segment_len()
.map_err(|source| with_segment_context("determining segment file size for resume", repo, offset, source))?;
if len <= segment::Header::LEN as u64 {
debug!("repo {}: segment {} is empty", repo, offset);
return Ok(ResumedSegment::Empty);
}
let guard_non_empty = |meta: &Metadata| match meta.tx_range.is_empty() {
true => Err(with_segment_context(
"checking metadata",
repo,
offset,
io::Error::new(io::ErrorKind::InvalidData, "no valid commits in segment"),
)),
false => Ok(()),
};
// The segment is now guaranteed to be non-empty, i.e. contain more bytes
// than the segment header.
//
// Traverse it to gather the `Metadata` and ensure that the segment is safe
// to resume, which is the case if:
//
// - it contains at least one commit
// - it does not contain corrupted commits
// - the existing segment passes the compatibility check
// - the existing segment's version is the same as
// the one requested in `opts`
let offset_index = repo.get_offset_index(offset).ok();
let meta = match Metadata::extract(offset, &mut reader, offset_index.as_ref()) {
Err(error::SegmentMetadata::InvalidCommit { sofar, source }) => {View on GitHub (pinned to 3653d2ed49)
Solutions
- Archive and remove the offending segment so resume continues from the previous valid segment, recovering lost transactions from a replica/upstream
- Prevent the torn tail in the first place: ensure graceful shutdown flushes commits, or trim trailing invalid data on open
- Verify disk and filesystem health if corruption is suspected
- Check that segment files copied into the repo were copied completely (checksum before restore)
Defensive patterns
Strategy: try-catch
Type guard
fn is_empty_segment_resume(err: &io::Error) -> bool {
err.kind() == io::ErrorKind::InvalidData && err.to_string().contains("no valid commits in segment")
} Try / catch
match commitlog.resume(&repo, &opts) {
Ok(state) => Ok(state),
Err(e) if is_empty_segment_resume(&e) => {
// quarantine the named segment and retry resume without it,
// recovering the gap from a replica/upstream
quarantine_last_segment(&repo)?;
commitlog.resume(&repo, &opts)
}
Err(e) => Err(e),
} Prevention
- Ensure graceful shutdown flushes the current segment, or trim trailing partial writes on open
- Keep a replica or upstream source so a quarantined segment's data can be re-streamed
- Checksum backup files before restoring them into the repo directory
When it happens
Trigger: Resuming after a crash that left a partially written or torn first commit in a fresh segment; a segment file preallocated with zeroes and longer than the header; on-disk corruption in the first commit record; copying a truncated segment file into the repo directory.
Common situations: Unclean shutdown (kill -9, power loss) immediately after segment creation; restoring partial backups; disk corruption or bit rot on the segment's first records.
Related errors
- segment header does not start with magic: expected {:02x?},
- out-of-order offset: expected={} actual={}
- Error looking up name for truncated table {table_id:?}
- Delete for non-existent row when replaying transaction
- repo {}: first commit in the most recent segment is corrupt:
AI-assisted analysis of clockworklabs/SpacetimeDB@3653d2ed49 (2026-08-20).
Data as JSON: /api/errors/13a2fdd74fcc2c4d.
Report an issue: GitHub.