{"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":"eyre::Report","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/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/crates/mise-cache-core/src/local.rs#L159-L195","documentation":"LocalActionCache::path_for — and therefore find/store — requires action digests to use blake3, mirroring the remote client's rule for action keys. Blob digests in the CAS may use sha256, but the local action-result index (action-results/v1/blake3/<hash[0..2]>/<hash>-<size>.json) only accepts blake3 keys.","triggerScenarios":"Passing a sha256 CacheDigest to LocalActionCache::find or store; reusing a blob digest as an action key; porting an integration that indexed action results by sha256 before this crate pinned blake3.","commonSituations":"Digest plumbing where blob digests (any supported algorithm) and action keys (blake3 only) flow through the same helper; upgrades from earlier prototypes; tests sharing fixture digests between blobs and actions.","solutions":["Build action keys with CacheDigest::blake3(&canonical_json(&result)?)","Keep one dedicated code path for action keys (blake3) even when blob digests use sha256","Assert action.algorithm == \"blake3\" before calling find/store so misuse fails at the call site"],"exampleFix":"// before\nlet action = CacheDigest { algorithm: \"sha256\".into(), .. };\nactions.find(&action)?;\n\n// after\nlet action = CacheDigest::blake3(&canonical_json(&result)?);\nactions.find(&action)?;","handlingStrategy":"validation","validationCode":"fn local_action_key(result: &RemoteActionResult) -> eyre::Result<CacheDigest> {\n    let bytes = mise_cache_core::canonical_json(result)?;\n    Ok(CacheDigest::blake3(&bytes)) // local action keys are always blake3\n}","typeGuard":"fn is_blake3_key(digest: &CacheDigest) -> bool {\n    digest.algorithm == \"blake3\" && digest.validate().is_ok()\n}","tryCatchPattern":null,"preventionTips":["Use one blake3-only helper for action keys, separate from blob digest code","Assert the algorithm before LocalActionCache::find/store calls","Keep test fixtures for keys and blobs distinct so they cannot be swapped"],"tags":["local-cache","action-result","blake3","digest"],"backgroundTag":"digest-algorithm-mismatch","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}