{"record":{"id":"429ffb4bb406a3b6","repo":"jdx/mise","slug":"remote-cache-blob-failed-digest-verification","errorCode":null,"errorMessage":"remote cache blob failed digest verification","messagePattern":"remote cache blob failed digest verification","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/lib.rs","lineNumber":482,"sourceCode":"    }\n\n    pub async fn get_blob(\n        &self,\n        digest: &CacheDigest,\n        media_type: &'static str,\n    ) -> Result<Vec<u8>> {\n        digest.validate()?;\n        let url = self.blob_endpoint(digest)?;\n        retry_async(\"GET\", &url, self.retries, || async {\n            let response = self\n                .request(reqwest::Method::GET, url.clone(), media_type)\n                .await?\n                .send()\n                .await?\n                .error_for_status()?;\n            let bytes = response.bytes().await?.to_vec();\n            if !digest.matches_bytes(&bytes)? {\n                bail!(\"remote cache blob failed digest verification\");\n            }\n            Ok(bytes)\n        })\n        .await\n    }\n\n    pub async fn get_blob_file(\n        &self,\n        digest: &CacheDigest,\n        staging_dir: &Path,\n    ) -> Result<tempfile::NamedTempFile> {\n        let url = self.blob_endpoint(digest)?;\n        let download = retry_async(\"GET\", &url, self.retries, || async {\n            let mut response = self\n                .request(reqwest::Method::GET, url.clone(), BLOB_MEDIA_TYPE)\n                .await?\n                .send()\n                .await?;","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/crates/mise-cache-core/src/lib.rs#L464-L500","documentation":"get_blob downloads a blob into memory and verifies both the byte length and the hash against the requested digest via matches_bytes. A mismatch means the server returned different bytes than the content-addressed key promised — corruption, truncation, or a misbehaving server. The bail! is not classified as transient by retry_async (only reqwest-level failures are), so it is not automatically retried.","triggerScenarios":"Calling get_blob for a digest the server stored incorrectly; a proxy truncating or modifying the body; a server that resolves the wrong object for the algorithm/hash/size path; an upload that was recorded as complete before the body finished.","commonSituations":"Bit corruption or partial writes on the cache server; gateways that rewrite bodies (compression, AV scanning); hostile interception on an unauthenticated plain-HTTP link; a server bug after a version upgrade.","solutions":["Treat the error as a cache miss and rebuild the content locally instead of retrying the same digest","Once correct bytes are confirmed locally, re-upload via put_blob (If-None-Match: *); a wrongly-stored existing entry may additionally need server-side deletion","Check intermediaries that rewrite bodies for the blob media type (application/octet-stream)","If only one hash consistently fails while others work, delete that object server-side and re-upload"],"exampleFix":"// before\nlet bytes = client.get_blob(&digest, media).await?;\n\n// after: fall back to rebuilding on digest mismatch\nlet bytes = match client.get_blob(&digest, media).await {\n    Ok(bytes) => bytes,\n    Err(report) if report.to_string().contains(\"failed digest verification\") => {\n        rebuild_locally(&digest)?\n    }\n    Err(report) => return Err(report),\n};","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"let bytes = match client.get_blob(&digest, media).await {\n    Ok(bytes) => bytes,\n    Err(report) if report.to_string().contains(\"failed digest verification\") => {\n        rebuild_locally(&digest)? // miss, not a retryable network error\n    }\n    Err(report) => return Err(report),\n};","preventionTips":["Always upload via put_blob from verified local bytes so stored entries match their keys","Keep transforming proxies off application/octet-stream traffic","Delete and re-upload a server object that fails verification for the same digest twice"],"tags":["remote-cache","blob","digest-verification","integrity"],"backgroundTag":"checksum-verification-failed","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}