{"record":{"id":"3434eb3c06a8a42e","repo":"jdx/mise","slug":"remote-action-manifest-etag-does-not-match-its-bod","errorCode":null,"errorMessage":"remote action manifest ETag does not match its body","messagePattern":"remote action manifest ETag does not match its body","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/lib.rs","lineNumber":723,"sourceCode":"        let url = self.action_manifest_endpoint(key)?;\n        retry_async(\"GET\", &url, self.retries, || async {\n            let response = self\n                .request(\n                    reqwest::Method::GET,\n                    url.clone(),\n                    TASK_ACTION_MANIFEST_MEDIA_TYPE,\n                )\n                .await?\n                .send()\n                .await?;\n            if response.status() == StatusCode::NOT_FOUND {\n                return Ok(None);\n            }\n            let response = response.error_for_status()?;\n            let etag = parse_strong_etag(response.headers().get(ETAG))?;\n            let bytes = response.bytes().await?.to_vec();\n            if blake3::hash(&bytes).to_hex().as_str() != etag {\n                bail!(\"remote action manifest ETag does not match its body\");\n            }\n            Ok(Some(RemoteActionManifest { bytes, etag }))\n        })\n        .await\n    }\n\n    pub async fn put_action_manifest(\n        &self,\n        key: &CacheDigest,\n        bytes: &[u8],\n        expected_etag: Option<&str>,\n    ) -> Result<ManifestPutOutcome> {\n        let url = self.action_manifest_endpoint(key)?;\n        let body = bytes.to_vec();\n        let expected_etag = expected_etag.map(quoted_etag).transpose()?;\n        retry_async(\"PUT\", &url, self.retries, || async {\n            let mut request = self\n                .request(","sourceCodeStart":705,"sourceCodeEnd":741,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/crates/mise-cache-core/src/lib.rs#L705-L741","documentation":"Thrown on action-manifest GET (crates/mise-cache-core/src/lib.rs:723): the response must carry a strong ETag, and blake3 of the response body (hex) must equal that ETag string (parse_strong_etag extracts it). If hashing the body does not reproduce the ETag, the manifest body and its validator disagree — the client refuses to trust the bytes.","triggerScenarios":"Calling get_action_manifest where the server's ETag header is not the blake3 hex of the exact body — e.g. the server computes the ETag before final serialization, re-serializes differently (key order, whitespace), uses a weak ETag or an md5-based one, or a proxy modifies the body (recompression) after the ETag was generated.","commonSituations":"Custom servers deriving ETags from mtime or len rather than content; serde/JSON re-serialization changing byte layout between ETag computation and write; compression in transit; server frameworks that append a suffix to ETags.","solutions":["If you operate the server, set ETag to the strong blake3 hex of the exact bytes written: format!(\"\\\"{}\\\"\", blake3::hash(&bytes).to_hex())","Ensure no intermediary recompresses or rewrites manifest responses for the action-manifests routes","If using a generic HTTP framework's auto-ETag, disable it and compute the blake3 ETag explicitly","Retry once to rule out an in-flight body mutation race, then report to the server operator"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// server-side helper to guarantee agreement:\nfn manifest_response(bytes: &[u8]) -> Response {\n    let etag = format!(\"\\\"{}\\\"\", blake3::hash(bytes).to_hex());\n    Response::new(bytes).with_header(ETAG, etag)\n}","typeGuard":null,"tryCatchPattern":"match client.get_action_manifest(&key).await {\n    Err(e) if e.to_string().contains(\"ETag does not match\") => {\n        // body untrusted: treat as miss and re-upload the manifest\n        client.put_action_manifest(&key, &local_bytes, None).await?;\n    }\n    other => other?,\n}","preventionTips":["Compute ETags from the exact serialized bytes at write time, never from a pre-serialization value","Disable framework auto-ETag (mtime/len based) on manifest routes","Keep manifest responses uncompressed or ensure ETag covers the same byte stream the client receives"],"tags":["mise","cache","http","etag","integrity","action-manifest"],"backgroundTag":"etag-mismatch","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}