{"record":{"id":"4ecf2bea3d92b8cd","repo":"jdx/mise","slug":"remote-cache-action-keys-must-use-blake3","errorCode":null,"errorMessage":"remote cache action keys must use blake3","messagePattern":"remote cache action keys must use blake3","errorType":"validation","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/lib.rs","lineNumber":307,"sourceCode":"            .connect_timeout(config.connect_timeout)\n            .read_timeout(config.read_timeout)\n            .redirect(reqwest::redirect::Policy::none())\n            .build()?;\n        let credential = remote_credential(&config, client.clone())?;\n        Ok(Self {\n            base_url: normalized_base_url(config.base_url),\n            namespace: config.namespace,\n            client,\n            credential,\n            download_timeout: config.download_timeout,\n            retries: config.retries,\n        })\n    }\n\n    fn action_result_endpoint(&self, action: &CacheDigest) -> Result<Url> {\n        action.validate()?;\n        if action.algorithm != \"blake3\" {\n            bail!(\"remote cache action keys must use blake3\");\n        }\n        Ok(self.base_url.join(&format!(\n            \"v{PROTOCOL_VERSION}/action-results/{}/{}/{}\",\n            action.algorithm, action.hash, action.size\n        ))?)\n    }\n\n    fn blob_endpoint(&self, digest: &CacheDigest) -> Result<Url> {\n        digest.validate()?;\n        Ok(self.base_url.join(&format!(\n            \"v{PROTOCOL_VERSION}/blobs/{}/{}/{}\",\n            digest.algorithm, digest.hash, digest.size\n        ))?)\n    }\n\n    fn action_manifest_endpoint(&self, key: &CacheDigest) -> Result<Url> {\n        key.validate()?;\n        if key.algorithm != \"blake3\" {","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/crates/mise-cache-core/src/lib.rs#L289-L325","documentation":"Although blob digests may use either blake3 or sha256, action-result keys must use blake3 because the action digest is defined as blake3 over the canonical JSON of the action record. action_result_endpoint() rejects any action CacheDigest whose algorithm is not \"blake3\" before building the request URL. The rule applies to both get_action_result and put_action_result.","triggerScenarios":"Passing a sha256 CacheDigest as the action key to RemoteCacheClient::get_action_result/put_action_result; reusing a blob digest (which may legitimately be sha256) as an action key; building a RemoteActionResult whose action field was hashed with sha256.","commonSituations":"Porting code that hashed everything with sha256 before blake3 became the key algorithm; mixing blob digests and action digests in the same struct or map; test fixtures written with sha256 action keys (the unit test action_result_keys_require_blake3 pins this behavior).","solutions":["Compute action keys with CacheDigest::blake3(&canonical_json(&action_record)?) — never sha256","If migrating from sha256 keys, recompute digests and re-upload; old server entries simply become cache misses","Assert action.algorithm == \"blake3\" in your code before calling get/put_action_result so the failure is localized"],"exampleFix":"// before\nlet action = CacheDigest { algorithm: \"sha256\".into(), hash: sha256_of_record, size };\nclient.get_action_result(&action).await?;\n\n// after\nuse mise_cache_core::{canonical_json, CacheDigest};\nlet action = CacheDigest::blake3(&canonical_json(&record)?);\nclient.get_action_result(&action).await?;","handlingStrategy":"validation","validationCode":"fn action_key(record: &impl serde::Serialize) -> eyre::Result<CacheDigest> {\n    let bytes = mise_cache_core::canonical_json(record)?;\n    Ok(CacheDigest::blake3(&bytes)) // action keys are always blake3\n}\n\nfn ensure_action_key(action: &CacheDigest) -> eyre::Result<()> {\n    action.validate()?;\n    if action.algorithm != \"blake3\" {\n        eyre::bail!(\"action key must use blake3, got {}\", action.algorithm);\n    }\n    Ok(())\n}","typeGuard":"fn is_blake3_action_key(digest: &CacheDigest) -> bool {\n    digest.algorithm == \"blake3\" && digest.validate().is_ok()\n}","tryCatchPattern":null,"preventionTips":["Centralize action-digest computation in one helper that always uses blake3 over canonical_json","Reserve sha256 for blob digests only","Add debug_assert!(action.algorithm == \"blake3\") around get/put_action_result call sites"],"tags":["digest","blake3","action-result","remote-cache"],"backgroundTag":"digest-algorithm-mismatch","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}