{"record":{"id":"e7a8af3cb1e7554c","repo":"jdx/mise","slug":"local-action-result-is-invalid","errorCode":null,"errorMessage":"local action result is invalid: {}","messagePattern":"local action result is invalid: (.+?)","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/local.rs","lineNumber":196,"sourceCode":"        }\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 {\n            bail!(\"local action result is invalid: {}\", path.display());\n        }\n        Ok(Some(result))\n    }\n\n    /// Atomically publish an action result after validating all referenced objects.\n    pub fn store(&self, result: &RemoteActionResult) -> Result<PathBuf> {\n        if result.version != 1 {\n            bail!(\"unsupported local action result version\");\n        }\n        for digest in [\n            Some(&result.action),\n            result.metadata.as_ref(),\n            result.output_root.as_ref(),\n        ]\n        .into_iter()\n        .flatten()\n        {\n            if self.cas.find(digest)?.is_none() {","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/crates/mise-cache-core/src/local.rs#L178-L214","documentation":"`LocalActionCache::find` reads the stored JSON for an action digest and strictly validates it: `version` must be 1, the embedded `action` digest must equal the requested key, and the raw file bytes must equal the canonical re-serialization of the parsed result. Any deviation - hand edits, truncation, or a different serialization - makes the entry invalid and the error names the file. The store path self-heals: `store` detects an invalid existing entry and atomically replaces it.","triggerScenarios":"Reading an action-result JSON that changed after publishing: manual edits, a partial write from a crash in a pre-atomic version, tools that reformat the cache, or a cache directory shared with another version whose canonical JSON differs.","commonSituations":"Users inspecting or pretty-printing cache files; mixed-version cache directories; disk-cleanup tools truncating files.","solutions":["Re-store to heal: `LocalActionCache::store` replaces invalid entries via its `replace_invalid` path.","Or delete the named JSON file (or the `action-results/v1` subtree) - the next successful build republishes it.","Do not edit files under the cache root; give each tool version its own cache directory."],"exampleFix":"// before: the invalid entry propagates as an error\nlet result = actions.find(&action)?;\n\n// after: treat as a miss, then republish to heal\nlet result = match actions.find(&action) {\n    Ok(result) => result,\n    Err(_) => None, // recompute\n};\nlet fresh = recompute_result();\nactions.store(&fresh)?; // atomically replaces the invalid entry","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"let result = match actions.find(&action) {\n    Ok(result) => result,\n    Err(err) => {\n        warn!(\"invalid action result, treating as miss: {err}\");\n        None\n    }\n};\n// recompute on None, then actions.store(&fresh) heals the invalid entry atomically","preventionTips":["Treat action-cache find errors as misses and let store() replace invalid entries.","Never hand-edit JSON under action-results/v1.","Give each tool version its own cache root to avoid canonical-JSON mismatches."],"tags":["local-cache","action-cache","cache-corruption","json","rust"],"backgroundTag":"cache-corruption","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}