clockworklabs/SpacetimeDB · error · io::Error

all-zeroes commit header

Error message

all-zeroes commit header

What it means

While decoding the mirrored stream, the fixed-size commit header was read successfully but decoded as all zeroes, which commit::Header::decode rejects (EOF was already handled just above). Zero regions typically come from preallocated-but-unwritten file space or a torn write on the producer side, so the stream contains filler instead of commit data.

Solutions

  1. Stream only up to the last valid commit: bound the read to the committed length from segment metadata, not the file size
  2. Trim trailing invalid data on the source before streaming (the OnTrailingData hook used at StreamWriter::create exists for this)
  3. On the consumer, stop the stream at the first all-zero header instead of erroring, when partial data is acceptable
  4. Fix the producer to publish the committed offset alongside the data
Defensive patterns

Strategy: validation

Validate before calling

// On the producer side: stream only up to the committed length, not the file size
let committed_len = segment_metadata.size_in_bytes + segment::Header::LEN as u64;
let mut limited = (&mut file).take(committed_len); // never read preallocated zero tail
stream_from(&mut limited).await;

Type guard

fn is_all_zero_header(err: &io::Error) -> bool {
    err.kind() == io::ErrorKind::InvalidData && err.to_string().contains("all-zeroes commit header")
}

Try / catch

match writer.append_all(&mut stream, &mut progress).await {
    Ok(done) => Ok(done),
    Err(e) if is_all_zero_header(&e) => {
        // tail of preallocated/torn data reached: treat stream as ended, keep what was written
        Ok(StreamDone::Exhausted)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: The producer streamed a live segment up to its fallocated length instead of its committed length; the source crashed mid-write and the tail zeroes were included; zero padding was deliberately inserted into the stream.

Common situations: Mirroring by reading a segment file to file-size rather than to the last valid commit; snapshotting a file while it is being appended to; tail of a preallocated segment included by an off-by-length error.

Related errors


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

Appendix: source

Thrown at crates/commitlog/src/stream/writer.rs:306

                // The stream is exhausted, break the outer loop.
                trace!("eof");
                return Ok(AppendInnerResult::StreamExhausted);
            };
            if buf.starts_with(&segment::MAGIC) {
                // New segment, break inner loop.
                trace!("segment marker");
                return Ok(AppendInnerResult::SegmentMarker);
            }

            // Read the header, so we can determine the size of the commit.
            if read_exact(stream, &mut self.commit_buf.header).await?.is_eof() {
                return Ok(AppendInnerResult::StreamExhausted);
            }
            let Some(commit_header) = commit::Header::decode(&self.commit_buf.header[..])
                .inspect_err(|e| warn!("failed to decode commit header: {e}"))?
            else {
                // Nb. eof handled above.
                return Err(io::Error::new(io::ErrorKind::InvalidData, "all-zeroes commit header"));
            };

            // Read the rest of the commit.
            self.commit_buf.body.resize(
                commit_header.len as usize + CHECKSUM_LEN[current_segment.header.checksum_algorithm as usize],
                0,
            );
            stream.read_exact(&mut self.commit_buf.body).await?;
            // Decode the commit and verify its checksum.
            let commit = StoredCommit::decode(&mut self.commit_buf.as_reader(bytes_written))
                .inspect_err(|e| warn!("failed to decode commit: {e}"))?
                .expect("commit decode cannot return `None` because we already decoded the header");

            // Check that the commit offset is what we expect.
            let expected_offset = self
                .last_written_tx_range
                .as_ref()
                .map(|range| range.end)

View on GitHub (pinned to 9e0d92412f)