Hmbown/CodeWhale · critical

Fleet artifact checksum does not match the recorded receipt

Error message

Fleet artifact checksum does not match the recorded receipt

What it means

read_verified hashes the streamed bytes and requires the result to equal the sha256 checksum recorded in the FleetArtifactRef receipt ('sha256:<hex>'). A mismatch means the file content differs from what was published, so the artifact is not valid evidence and verification fails closed.

Solutions

  1. Treat the artifact as compromised: do not use it as evidence; re-publish the correct content via artifacts::write to get a fresh receipt.
  2. Restore the original file from a backup or the producing run's output.
  3. Verify the FleetArtifactRef actually belongs to this path/run — a copied or stale receipt will not match different content.
  4. Check storage health (disk, sync layer) if corruption recurs across artifacts.
Defensive patterns

Strategy: validation

Validate before calling

// trust nothing: verify receipt checksum before using artifact evidence
let (preview, size) = read_verified(ws, &artifact, preview_limit)?; // fails closed on mismatch
assert_eq!(artifact.size_bytes, Some(size));

Try / catch

match read_verified(ws, &artifact, preview_limit) {
    Err(e) if e.to_string().contains("checksum does not match") => {
        eprintln!("artifact {} is not valid evidence; re-publish from source", artifact.path.display());
    }
    other => other?,
}

Prevention

When it happens

Trigger: The bytes streamed from the artifact hash to a digest different from artifact.checksum — content was modified, corrupted, or the receipt belongs to a different file/version despite matching size.

Common situations: Bit rot or filesystem corruption; manual editing of a published artifact; a receipt copied from another run/path; a writer that bypassed artifacts::write and wrote different content at the same size.

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 Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/02dfb2522522d721. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/fleet/artifacts.rs:88

    // The digest and returned preview consume exactly the same bytes from the
    // same opened file. A changed/replaced pathname is never reopened for data.
    let mut reader = (&file).take(size + 1);
    loop {
        let count = reader.read(&mut buffer)?;
        if count == 0 {
            break;
        }
        total += count as u64;
        ensure!(total <= size, "Fleet artifact grew while being read");
        hasher.update(&buffer[..count]);
        let remaining = preview_limit.saturating_sub(preview.len() as u64) as usize;
        preview.extend_from_slice(&buffer[..count.min(remaining)]);
    }
    ensure!(
        total == size && file.metadata()?.len() == size,
        "Fleet artifact size changed while being read"
    );
    ensure!(
        format!("sha256:{}", crate::hashing::hex_bytes(hasher.finalize())) == checksum,
        "Fleet artifact checksum does not match the recorded receipt"
    );
    Ok((preview, size))
}

#[cfg(test)]
mod tests {
    use super::*;
    use codewhale_protocol::fleet::FleetArtifactKind;

    fn reference(path: &str, bytes: &[u8]) -> FleetArtifactRef {
        FleetArtifactRef {
            kind: FleetArtifactKind::Receipt,
            path: path.into(),
            checksum: Some(format!("sha256:{}", crate::hashing::sha256_hex(bytes))),
            mime_type: None,
            size_bytes: Some(bytes.len() as u64),

View on GitHub (pinned to 73e0f67d83)