{"record":{"id":"9e86f8d79c092432","repo":"jdx/mise","slug":"staged-blob-does-not-match-the-declared-cas-digest","errorCode":null,"errorMessage":"staged blob does not match the declared CAS digest","messagePattern":"staged blob does not match the declared CAS digest","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/local.rs","lineNumber":104,"sourceCode":"        verify: bool,\n    ) -> Result<PathBuf> {\n        let destination = self.path_for(digest)?;\n        if let Some(existing) = self.find(digest)? {\n            return Ok(existing);\n        }\n        let parent = destination.parent().expect(\"CAS path has a parent\");\n        fs::create_dir_all(parent)?;\n        let staging = tempfile::tempdir_in(parent)?;\n        let temporary = staging.path().join(\"blob\");\n        reflink_copy::reflink_or_copy(source, &temporary)?;\n        let temporary = tempfile::TempPath::try_from_path(temporary)?;\n        make_owner_writable(&temporary)?;\n        fs::OpenOptions::new()\n            .write(true)\n            .open(&temporary)?\n            .sync_all()?;\n        if verify && !digest.matches_file(&temporary)? {\n            bail!(\"staged blob does not match the declared CAS digest\");\n        }\n        if fs::metadata(&temporary)?.len() != digest.size {\n            bail!(\"staged blob size does not match the declared CAS digest\");\n        }\n        match temporary.persist_noclobber(&destination) {\n            Ok(()) => Ok(destination),\n            Err(error) if error.error.kind() == std::io::ErrorKind::AlreadyExists => self\n                .find(digest)?\n                .ok_or_else(|| eyre::eyre!(\"concurrent CAS write did not publish a valid blob\")),\n            Err(error) => Err(error.error.into()),\n        }\n    }\n\n    fn store_with(\n        &self,\n        digest: &CacheDigest,\n        write: impl FnOnce(&mut tempfile::NamedTempFile) -> Result<()>,\n    ) -> Result<PathBuf> {","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/crates/mise-cache-core/src/local.rs#L86-L122","documentation":"`store_file` reflinks or copies the source into a staging temp file and then re-hashes the staged copy against the declared digest before publishing. This error means the staged bytes do not match: in practice the source file changed between when the digest was computed and when the copy was made.","triggerScenarios":"The digest was computed from a file that is concurrently rewritten (build output still being written by the build, another process touching the file), or it was computed from a different path than the one passed to `store_file`.","commonSituations":"Caching build artifacts while the build still holds the file open; hashing then copying with an intervening mutation; network filesystems with weak read consistency.","solutions":["Recompute the digest immediately before staging: `let digest = CacheDigest::blake3_file(&path)?; cas.store_file(&digest, &path)?` and ensure no writer is active.","If the source may still be mutating, copy it to a stable location first, then hash and store the copy.","Retry once the writer has finished; a racing writer that has since exited makes the next attempt succeed."],"exampleFix":"// before: digest computed earlier, file changed since\ncas.store_file(&stale_digest, &path)?; // bails\n\n// after: hash the current file right before storing\nlet digest = CacheDigest::blake3_file(&path)?;\ncas.store_file(&digest, &path)?;","handlingStrategy":"validation","validationCode":"// hash the file right before staging so mutation windows are minimal\nlet digest = CacheDigest::blake3_file(&source)?;\nif !digest.matches_file(&source)? {\n    // source is still changing; wait for the writer to finish\n    return wait_and_retry();\n}\ncas.store_file(&digest, &source)?;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only cache files after the producing build has fully written and closed them.","For mutable sources, copy to a stable path first and hash/store the copy.","Keep digest computation and store_file adjacent in code - no awaits or other work between them."],"tags":["local-cache","cas","checksum-mismatch","race-condition","digest","rust"],"backgroundTag":"checksum-mismatch","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}