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
- Compute the digest immediately before storing, from the exact same slice: CacheDigest::blake3(bytes) then cas.store_bytes(&digest, bytes)
- Hash the same serialization you store — use canonical_json consistently for protocol objects
- 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
- Compute the digest from the same buffer in the same expression as the store
- Use canonical_json consistently for protocol objects — never mix serde_json::to_vec and canonical_json
- Snapshot concurrent content into a Vec before hashing and storing
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
- local CAS blob failed digest verification: {}
- staged blob does not match the declared CAS digest
- staged blob size does not match the declared CAS digest
- unsupported remote cache digest algorithm
- invalid remote cache digest
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/928926baaa200370.
Report an issue: GitHub.