{"record":{"id":"79a4b0b3186b3728","repo":"jdx/mise","slug":"local-action-keys-must-use-blake3","errorCode":null,"errorMessage":"local action keys must use blake3","messagePattern":"local action keys must use blake3","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/local.rs","lineNumber":177,"sourceCode":"    fs::set_permissions(path, permissions)?;\n    Ok(())\n}\n\nimpl LocalActionCache {\n    /// Create an action-result index beneath `root`.\n    pub fn new(root: impl Into<PathBuf>) -> Self {\n        let root = root.into();\n        Self {\n            cas: LocalCas::new(root.clone()),\n            root,\n        }\n    }\n\n    /// Resolve the storage path for an action digest.\n    pub fn path_for(&self, action: &CacheDigest) -> Result<PathBuf> {\n        action.validate()?;\n        if action.algorithm != \"blake3\" {\n            bail!(\"local action keys must use blake3\");\n        }\n        Ok(self\n            .root\n            .join(\"action-results/v1\")\n            .join(&action.algorithm)\n            .join(&action.hash[..2])\n            .join(format!(\"{}-{}.json\", action.hash, action.size)))\n    }\n\n    /// Find and strictly validate a canonical action result.\n    pub fn find(&self, action: &CacheDigest) -> Result<Option<RemoteActionResult>> {\n        let path = self.path_for(action)?;\n        if !path.exists() {\n            return Ok(None);\n        }\n        let bytes = fs::read(&path)?;\n        let result: RemoteActionResult = serde_json::from_slice(&bytes)?;\n        if result.version != 1 || result.action != *action || canonical_json(&result)? != bytes {","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/crates/mise-cache-core/src/local.rs#L159-L195","documentation":"`LocalActionCache::path_for` resolves the storage path for an action digest and requires the digest's algorithm to be exactly `blake3`. Sha-256 digests are valid for CAS blobs, but the action-result index is keyed by blake3 only, so any other algorithm is rejected before filesystem access. Every `find`/`store` call goes through `path_for`, so this guard covers the whole action cache API.","triggerScenarios":"Constructing a `CacheDigest` with `algorithm: \"sha256\"` (or any non-blake3 value) and passing it to `LocalActionCache::find`/`store`/`path_for`; deserializing an action digest from remote or JSON data that used sha256; reusing blob digests as action keys.","commonSituations":"Interop code hashing action manifests with sha-256 out of habit; mixing digest constructors between the blob CAS (blake3 or sha256) and the action cache (blake3 only); digests arriving from external systems.","solutions":["Compute action keys with `CacheDigest::blake3(bytes)` or `CacheDigest::blake3_file(&path)`.","When a digest arrives from external data, check `digest.algorithm == \"blake3\"` before using it as an action key.","Reserve sha-256 digests for blob storage only."],"exampleFix":"// before: sha256 action key - path_for bails\nlet action = CacheDigest { algorithm: \"sha256\".into(), hash: sha256_hex, size };\nactions.find(&action)?;\n\n// after: action keys are blake3\nlet action = CacheDigest::blake3(&manifest_bytes);\nactions.find(&action)?;","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fn is_action_key(digest: &CacheDigest) -> bool {\n    digest.algorithm == \"blake3\" && digest.validate().is_ok()\n}\n\n// usage\nif is_action_key(&digest) {\n    let found = actions.find(&digest)?;\n} else {\n    // sha256 digests are blob keys only: store via LocalCas, never LocalActionCache\n}","tryCatchPattern":null,"preventionTips":["Have one constructor for action keys (always CacheDigest::blake3/blake3_file) and use it everywhere.","Guard deserialized digests with the type guard before touching the action cache.","Reserve sha256 for CAS blob digests."],"tags":["local-cache","action-cache","blake3","hash-algorithm","rust"],"backgroundTag":"unsupported-hash-algorithm","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}