jdx/mise · error · eyre::Report

bytes do not match the declared CAS digest

Error message

bytes do not match the declared CAS digest

What it means

LocalCas::store_bytes verifies the supplied bytes against the declared digest (hash and length) before writing anything, so the CAS never publishes content under a key it does not match. This error means the digest was computed from different bytes than the ones passed to the store.

Source

Thrown at crates/mise-cache-core/src/local.rs:60

    /// Find and verify a stored object.
    pub fn find(&self, digest: &CacheDigest) -> Result<Option<PathBuf>> {
        let path = self.path_for(digest)?;
        if !path.exists() {
            return Ok(None);
        }
        if !digest.matches_file(&path)? {
            bail!(
                "local CAS blob failed digest verification: {}",
                path.display()
            );
        }
        Ok(Some(path))
    }

    /// Atomically store bytes after verifying their declared digest.
    pub fn store_bytes(&self, digest: &CacheDigest, bytes: &[u8]) -> Result<PathBuf> {
        if !digest.matches_bytes(bytes)? {
            bail!("bytes do not match the declared CAS digest");
        }
        self.store_with(digest, |temporary| {
            temporary.write_all(bytes)?;
            Ok(())
        })
    }

    /// Atomically store a file after verifying its declared digest.
    pub fn store_file(&self, digest: &CacheDigest, source: &Path) -> Result<PathBuf> {
        self.store_file_inner(digest, source, true)
    }

    /// Store a file whose digest was already verified by this crate.
    pub(crate) fn store_verified_file(
        &self,
        digest: &CacheDigest,
        source: &Path,
    ) -> Result<PathBuf> {

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Compute the digest immediately before storing, from the exact same slice: CacheDigest::blake3(bytes) then cas.store_bytes(&digest, bytes)
  2. Hash the same serialization you store — use canonical_json consistently for protocol objects
  3. If content may change concurrently, snapshot it into a Vec first and hash/store the snapshot

Example fix

// before: digest and stored bytes diverge
let digest = CacheDigest::blake3(&record_bytes);
let serialized = serde_json::to_vec(&record)?; // different bytes
cas.store_bytes(&digest, &serialized)?;

// after: digest and store the exact same buffer
let bytes = canonical_json(&record)?;
let digest = CacheDigest::blake3(&bytes);
cas.store_bytes(&digest, &bytes)?;
Defensive patterns

Strategy: validation

Validate before calling

fn store_invariant(cas: &LocalCas, bytes: &[u8]) -> eyre::Result<PathBuf> {
    let digest = CacheDigest::blake3(bytes); // digest from the exact bytes being stored
    cas.store_bytes(&digest, bytes)
}

Prevention

When it happens

Trigger: Computing the digest from one buffer and passing different bytes to store_bytes — e.g. re-serializing JSON between hashing and storing; setting the size field independently of the data; hashing compressed data but storing the uncompressed form.

Common situations: Pipelines that regenerate content non-deterministically (map ordering, float formatting) between digest computation and storage; serializing with serde_json::to_vec for the digest but canonical_json for storage; unit tests with mismatched fixtures.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/928926baaa200370. Report an issue: GitHub.