astrid-runtime/astrid · critical

materialized file length differs from its descriptor

Error message

materialized file length differs from its descriptor

What it means

materialize rebuilds the file bytes from canonical chunk records and cross-checks the resulting Vec's length against the descriptor's logical_bytes. A mismatch means materialized content differs from the declared size, signaling data loss or corruption in the chunk records.

Solutions

  1. Rebuild the version from source so chunk records and logical_bytes are consistent.
  2. Verify each chunk record's canonical byte length matches its recorded length.
  3. Re-download/regenerate the evidence artifacts if they were produced elsewhere.
Defensive patterns

Strategy: try-catch

Validate before calling

let canonical: usize = chunks.iter().map(|c| record(c.id).canonical_len()).sum();
if canonical != logical_bytes as usize { return Err(anyhow!("canonical length drift")); }

Try / catch

match version.materialize() {
    Err(e) if e.to_string().contains("differs from its descriptor") => regenerate_evidence(),
    other => other,
}

Prevention

When it happens

Trigger: Calling materialize (used by delta_size) when the concatenated canonical chunk bytes total != self.logical_bytes — e.g. a chunk record's canonical bytes were re-encoded to a different length than originally counted.

Common situations: Chunk records transcoded or re-compressed between build and materialize; evidence files edited or truncated; mixing records from different measurement runs.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/caf949d91fe80d3a. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-storage-chunker-evidence/src/sketch.rs:451

    }

    fn record(&self, id: ObjectId) -> Result<&ObjectRecord> {
        self.records
            .binary_search_by_key(&id, |(object, _)| *object)
            .ok()
            .and_then(|index| self.records.get(index))
            .map(|(_, record)| record)
            .ok_or_else(|| anyhow::anyhow!("content DAG misses ordered chunk {id:?}"))
    }

    fn materialize(&self) -> Result<Vec<u8>> {
        let capacity = usize::try_from(self.logical_bytes)?;
        let mut bytes = Vec::with_capacity(capacity);
        for chunk in &self.chunks {
            bytes.extend_from_slice(self.record(chunk.id)?.canonical_bytes());
        }
        if bytes.len() != capacity {
            bail!("materialized file length differs from its descriptor");
        }
        Ok(bytes)
    }
}

fn collect_chunks(
    id: ObjectId,
    records: &BTreeMap<ObjectId, &ObjectRecord>,
    offset: &mut u64,
    chunks: &mut Vec<Chunk>,
) -> Result<()> {
    let record = records
        .get(&id)
        .copied()
        .ok_or_else(|| anyhow::anyhow!("content DAG misses {id:?}"))?;
    match record.kind() {
        ObjectKind::Chunk => {
            let length = u64::try_from(record.canonical_bytes().len())?;

View on GitHub (pinned to affd8760f4)