{"record":{"id":"23c6ec42dc306f6b","repo":"jdx/mise","slug":"cached-oci-layer-digest-mismatch","errorCode":null,"errorMessage":"cached OCI layer digest mismatch","messagePattern":"cached OCI layer digest mismatch","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/oci/layer/cache.rs","lineNumber":131,"sourceCode":"    hash.update(bytes);\n}\n\nfn read_cached_layer(record_path: &Path, cache_dir: &Path) -> Result<Option<LayerBlob>> {\n    let record = match std::fs::read(record_path) {\n        Ok(bytes) => bytes,\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n        Err(err) => return Err(err.into()),\n    };\n    let record: CachedLayer = serde_json::from_slice(&record)?;\n    crate::oci::layout::validate_sha256_digest(&record.digest)?;\n    crate::oci::layout::validate_sha256_digest(&record.diff_id)?;\n    let bytes = std::fs::read(cache_dir.join(record.digest.trim_start_matches(\"sha256:\")))?;\n    eyre::ensure!(\n        bytes.len() as u64 == record.size,\n        \"cached OCI layer size mismatch\"\n    );\n    let digest = format!(\"sha256:{}\", hex_encode(&Sha256::digest(&bytes)));\n    eyre::ensure!(digest == record.digest, \"cached OCI layer digest mismatch\");\n    let mut decoder = flate2::read::GzDecoder::new(bytes.as_slice());\n    let mut hash = Sha256::new();\n    let mut buffer = [0; 64 * 1024];\n    loop {\n        let n = decoder.read(&mut buffer)?;\n        if n == 0 {\n            break;\n        }\n        hash.update(&buffer[..n]);\n    }\n    let diff_id = format!(\"sha256:{}\", hex_encode(&hash.finalize()));\n    eyre::ensure!(\n        diff_id == record.diff_id,\n        \"cached OCI layer diff ID mismatch\"\n    );\n    Ok(Some(LayerBlob {\n        digest,\n        diff_id,","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/oci/layer/cache.rs#L113-L149","documentation":"While validating a cached OCI layer in read_cached_layer (src/oci/layer/cache.rs:131), mise recomputes the SHA-256 of the gzipped layer blob on disk and compares it to the digest recorded in the cache metadata. This error means the content hash does not match `record.digest`: the blob file was modified, corrupted, or swapped after being cached, even though its size happened to match.","triggerScenarios":"Calling read_cached_layer (via build_cached_tool_layer) where bytes.len() == record.size but sha256(bytes) != record.digest. Causes include bit-rot/corruption, in-place tampering or manual edits, a hash-collision-shaped bug (overwritten blob of identical size from another layer), or unsafe shared-cache writes.","commonSituations":"Cache directories shared across machines or manipulated by other tools; disk corruption where file size survives but content changed; manually 'fixing' a cache entry by copying a different layer file in; concurrent builds racing on the same cache path.","solutions":["Delete the affected cached layer (file named <digest> in the OCI cache dir) — or the whole OCI cache — and let mise re-download/rebuild it.","Re-run the layer-producing operation online so the cache is repopulated from the trusted source.","Investigate disk/filesystem health if multiple entries fail validation (memory or disk corruption).","Avoid manual writes into the OCI cache and don't share one cache dir concurrently across independent builds."],"exampleFix":"# before: digest mismatch on layer sha256:abc...\nrm ~/.cache/mise/oci/layers/sha256:abc...\n# after: layer re-fetched and validated\nmise build-push ...","handlingStrategy":"fallback","validationCode":"let bytes = std::fs::read(blob_path)?;\nlet actual = Sha256::digest(&bytes);\nif format!(\"sha256:{x}\", x = hex::encode(actual)) != record.digest {\n  eprintln!(\"cache digest mismatch; invalidating\");\n  std::fs::remove_file(blob_path)?;\n}","typeGuard":null,"tryCatchPattern":"match read_cached_layer(...) {\n  Err(e) if e.to_string().contains(\"digest mismatch\") => {\n    invalidate_cache_entry(digest);\n    fetch_layer_fresh(digest)\n  }\n  other => other,\n}","preventionTips":["Treat the OCI cache as opaque — never write or swap files in it","Avoid concurrent builds sharing one cache directory","Investigate disk health if validation failures recur"],"tags":["oci","cache","integrity","sha256"],"backgroundTag":"checksum-mismatch","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}