{"record":{"id":"d287fd9cf9711a9c","repo":"jdx/mise","slug":"invalid-remote-action-manifest-etag","errorCode":null,"errorMessage":"invalid remote action manifest ETag","messagePattern":"invalid remote action manifest ETag","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/lib.rs","lineNumber":572,"sourceCode":"        .await\n    }\n}\n\nfn parse_strong_etag(value: Option<&HeaderValue>) -> Result<String> {\n    let value = value\n        .and_then(|value| value.to_str().ok())\n        .ok_or_else(|| eyre!(\"remote action manifest response is missing an ETag\"))?;\n    let etag = value\n        .strip_prefix('\"')\n        .and_then(|value| value.strip_suffix('\"'))\n        .filter(|value| is_lower_hex_digest(value))\n        .ok_or_else(|| eyre!(\"remote action manifest response has an invalid ETag\"))?;\n    Ok(etag.to_owned())\n}\n\nfn quoted_etag(etag: &str) -> Result<HeaderValue> {\n    if !is_lower_hex_digest(etag) {\n        bail!(\"invalid remote action manifest ETag\");\n    }\n    Ok(HeaderValue::from_str(&format!(\"\\\"{etag}\\\"\"))?)\n}\n\nfn is_lower_hex_digest(value: &str) -> bool {\n    value.len() == 64\n        && value\n            .bytes()\n            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))\n}\n\n#[derive(Clone)]\nenum RemoteCacheCredential {\n    None,\n    Static(HeaderValue),\n    File(PathBuf),\n    GithubActions(Arc<GithubActionsOidcCredential>),\n}","sourceCodeStart":554,"sourceCodeEnd":590,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/crates/mise-cache-core/src/lib.rs#L554-L590","documentation":"put_action_manifest's expected_etag parameter must be the unquoted, 64-character lowercase blake3 hex digest — exactly the string returned in RemoteActionManifest.etag by get_action_manifest. quoted_etag() validates the caller-supplied value before the request is sent and rejects anything else: surrounding quotes, a W/ weak-ETag prefix, uppercase hex, or a wrong-length string.","triggerScenarios":"Passing the raw ETag response-header value (which includes quotes) verbatim; wrapping the etag in quotes yourself; using a weak ETag (W/\"...\"); passing a sha256-based etag; persisting the etag through a layer that uppercases hex.","commonSituations":"Round-tripping etags through a proxy or cache layer that keeps header quoting; storing etags in a database with case-folding; hand-building the optimistic-concurrency update instead of using the value from get_action_manifest.","solutions":["Pass manifest.etag (from get_action_manifest) directly as expected_etag, with no modification","If you persist an etag between runs, store it unquoted and lowercase, exactly as delivered","Never build expected_etag from raw HTTP response headers; source it exclusively from this crate's API"],"exampleFix":"// before: forwarding a quoted header value\nclient\n    .put_action_manifest(&key, &bytes, Some(&format!(\"\\\"{etag}\\\"\")))\n    .await?;\n\n// after: pass the etag exactly as get_action_manifest returned it\nclient\n    .put_action_manifest(&key, &bytes, Some(manifest.etag.as_str()))\n    .await?;","handlingStrategy":"validation","validationCode":"fn is_unquoted_blake3_etag(etag: &str) -> bool {\n    !etag.starts_with('\"')\n        && !etag.starts_with(\"W/\")\n        && etag.len() == 64\n        && etag.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))\n}\n\n// gate the call before put_action_manifest\nif let Some(etag) = &expected_etag {\n    assert!(is_unquoted_blake3_etag(etag), \"etag must come from get_action_manifest\");\n}","typeGuard":"fn is_valid_expected_etag(etag: Option<&str>) -> bool {\n    etag.map_or(true, |e| {\n        e.len() == 64 && e.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))\n    })\n}","tryCatchPattern":null,"preventionTips":["Always pass RemoteActionManifest.etag verbatim as expected_etag","Never construct expected_etag from raw response headers","If persisting etags, store them unquoted/lowercase and re-validate on load"],"tags":["etag","manifest","validation","remote-cache"],"backgroundTag":"etag-mismatch","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}