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

failed to decode commit header field '{field}': {e}

Error message

failed to decode commit header field '{field}': {e}

What it means

Defensive decoder error from Header decoding: the fixed header buffer is parsed field-by-field (min_tx_offset u64, epoch u64, n u16, len u32) and a DecodeError from spacetimedb_sats::buffer is wrapped with the failing field's name via decode_header_error. Because the header is read into a stack buffer sized exactly Header::LEN (22 bytes in v1, 14 in v0 - exactly covering the fields), this path cannot fire for on-disk data; it only triggers if the buffer length and the decode sequence disagree, i.e. a crate bug or version skew.

Source

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

        let size_in_bytes = Commit::from(commit).encoded_len() as u64;

        Self {
            tx_range,
            size_in_bytes,
            epoch,
            checksum,
        }
    }
}

fn decode_u32<R: Read>(mut read: R) -> io::Result<u32> {
    let mut buf = [0; 4];
    read.read_exact(&mut buf)?;
    Ok(u32::from_le_bytes(buf))
}

fn decode_header_error(e: DecodeError, field: &str) -> io::Error {
    invalid_data(format!("failed to decode commit header field '{field}': {e}"))
}

fn invalid_data<E>(e: E) -> io::Error
where
    E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    io::Error::new(io::ErrorKind::InvalidData, e)
}

#[cfg(test)]
mod tests {
    use std::num::NonZeroU8;

    use proptest::prelude::*;

    use super::*;
    use crate::{payload::ArrayDecoder, tests::helpers::enable_logging, DEFAULT_LOG_FORMAT_VERSION};

View on GitHub (pinned to 9e0d92412f)

Solutions

  1. Align all workspace members and nodes on a single SpacetimeDB/commitlog version and rebuild from clean state.
  2. Report upstream with the exact field name from the message - it identifies which read overran and pinpoints the layout mismatch.
Defensive patterns

Strategy: try-catch

Try / catch

match reader.next_commit() {
    Ok(Some(c)) => commits.push(c),
    Ok(None) => {}
    Err(e) if e.kind() == io::ErrorKind::InvalidData
        && e.to_string().contains("failed to decode commit header field") =>
    {
        // layout/version skew inside the crate: unrecoverable - stop and escalate
        return Err(e.into());
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Effectively unreachable through public APIs: it would require decoding a header from a buffer shorter than the fields being read, which the fixed-size read_exact makes impossible. Realistic only with a vendored/patched commitlog whose Header::LEN differs from the field sequence, or mixed crate versions writing/reading the log.

Common situations: Mixing commitlog crate versions (forks with a different header layout); otherwise not observed.

Understand the failure class

Related errors


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