clockworklabs/SpacetimeDB · error · std::io::Error
error reading commit header
Error message
error reading commit header: {e} What it means
Low-level commitlog error raised while decoding a commit header (Header::decode_v1 in the commitlog crate). The decoder reads the fixed-size v1 header (Header::LEN = 22 bytes: min_tx_offset u64, epoch u64, n u16, len u32); a clean end-of-file at that boundary is normal and returns Ok(None) so preallocated zeroed segments are allowed. Any other I/O failure is re-wrapped with this message, preserving the underlying io::ErrorKind.
Solutions
- Check the data directory: permissions on segment files, disk health (dmesg/SMART), free space.
- Ensure no second process (another node, a truncating backup job, manual tooling) mutates segment files while the log is being read.
- If the media is damaged, restore the commitlog directory from a snapshot or replica.
Defensive patterns
Strategy: try-catch
Validate before calling
// Fail fast if segment files are not readable before replay let meta = std::fs::metadata(&segment_path)?; anyhow::ensure!(meta.permissions().readonly() == false, "segment file is read-only");
Try / catch
match Header::decode(&mut reader) {
Ok(Some(hdr)) => { /* process commit */ }
Ok(None) => {} // clean end of log (EOF at header boundary or zeroed preallocated bytes)
Err(e) => {
// UnexpectedEof is already normalized to Ok(None) by the library;
// anything reaching here is a real storage fault - surface it, do not retry blindly.
return Err(e.into());
}
} Prevention
- Keep the commitlog data directory on local durable storage, not NFS/network volumes.
- Grant the node process stable ownership of its data directory; never chmod or chown underneath it while running.
- Monitor the storage layer for IO errors - this error is a symptom of a lower-level fault, not the disease.
When it happens
Trigger: Replaying or opening a commitlog segment when the storage read fails partway through the header byte range with something other than UnexpectedEof - e.g. PermissionDenied, device/IO errors, or a failing network filesystem under the data directory.
Common situations: Data-directory permissions changed under a running node; failing or disconnecting disks/NFS; another process truncating or deleting segment files during replay.
Related errors
- failed to flush segment upon rotation
- allocation size overflow
- failed to read bytes of commit payload
- Failed to truncate offset index
- invalid seek to a negative or overflowing position
AI-assisted analysis of clockworklabs/SpacetimeDB@9e0d92412f (2026-08-20).
Data as JSON: /api/errors/ba3cf912c2c3fb36.
Report an issue: GitHub.
Appendix: source
Thrown at crates/commitlog/src/commit.rs:92
Ok(Some(Self {
min_tx_offset,
epoch: Commit::DEFAULT_EPOCH,
n,
len,
}))
}
}
}
fn decode_v1<R: Read>(mut reader: R) -> io::Result<Option<Self>> {
let mut hdr = [0; Self::LEN];
if let Err(e) = reader.read_exact(&mut hdr) {
if e.kind() == io::ErrorKind::UnexpectedEof {
return Ok(None);
}
return Err(io::Error::new(e.kind(), format!("error reading commit header: {e}")));
}
match &mut hdr.as_slice() {
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] => Ok(None),
buf => {
let min_tx_offset = buf.get_u64().map_err(|e| decode_header_error(e, "min_tx_offset"))?;
let epoch = buf.get_u64().map_err(|e| decode_header_error(e, "epoch"))?;
let n = buf.get_u16().map_err(|e| decode_header_error(e, "n"))?;
let len = buf.get_u32().map_err(|e| decode_header_error(e, "len"))?;
Ok(Some(Self {
min_tx_offset,
epoch,
n,
len,
}))
}
}
}View on GitHub (pinned to 9e0d92412f)