{"record":{"id":"d81a448bc6ac08b5","repo":"jdx/mise","slug":"cached-description-is-not-canonical-json","errorCode":null,"errorMessage":"cached {description} is not canonical JSON","messagePattern":"cached (.+?) is not canonical JSON","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/cache/rustc.rs","lineNumber":520,"sourceCode":"        AgentResponse::Blob { path: Some(path) } if digests.len() == 1 => Ok(vec![path]),\n        AgentResponse::Blob { path: None } if digests.len() == 1 => {\n            let digest = &digests[0];\n            bail!(\"cached rustc action is missing blob {}\", digest.hash)\n        }\n        AgentResponse::Error { message } => bail!(message),\n        _ => bail!(\"cache agent returned an unexpected blob lookup response\"),\n    }\n}\n\nfn read_canonical_blob<T>(path: &Path, digest: &CacheDigest, description: &str) -> Result<T>\nwhere\n    T: DeserializeOwned + Serialize,\n{\n    let bytes = read_verified_blob(path, digest, description)?;\n    let value = serde_json::from_slice(&bytes)\n        .wrap_err_with(|| format!(\"cached {description} is not valid JSON\"))?;\n    if canonical_json(&value)? != bytes {\n        bail!(\"cached {description} is not canonical JSON\");\n    }\n    Ok(value)\n}\n\nfn read_verified_blob(path: &Path, digest: &CacheDigest, description: &str) -> Result<Vec<u8>> {\n    let mut bytes = Vec::new();\n    std::fs::File::open(path)?.read_to_end(&mut bytes)?;\n    if !digest.matches_bytes(&bytes)? {\n        bail!(\"cached {description} failed digest verification\");\n    }\n    Ok(bytes)\n}\n\nfn validated_outputs(\n    directory: CacheDirectory,\n    outputs: &RustcOutputs,\n) -> Result<Vec<(CacheFileNode, PathBuf)>> {\n    if directory.version != 1 || !directory.directories.is_empty() || !directory.symlinks.is_empty()","sourceCodeStart":502,"sourceCodeEnd":538,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/cache/rustc.rs#L502-L538","documentation":"read_canonical_blob() enforces structural integrity on cached JSON blobs: it parses the bytes, re-serializes with canonical_json, and requires byte equality (src/cache/rustc.rs:516-521). The blob passed the blake3 digest check but is not the canonical encoding, so it was produced by a different serializer version or otherwise diverges from what this build would have written; the entry is treated as untrusted and rejected. It applies to the RustcMetadata and CacheDirectory descriptors read during restore_result (src/cache/rustc.rs:310-316).","triggerScenarios":"Restoring a metadata or output-directory blob written by a mise version whose canonical JSON encoding (field order, escaping, number formatting) differs; a remote cache namespace populated by clients on a different mise version; cache files rewritten in place by external tooling while the digest list still matches by coincidence of write-order.","commonSituations":"Sharing one cache directory or remote namespace across mise upgrades; CI caches populated by an older image; hand-edited cache files.","solutions":["Clear the affected cache (local directory and, if you control it, the remote namespace) and let it repopulate from a single mise version","Pin one mise version per shared cache/namespace, or version the namespace string with the mise version","If it appears without any version change, treat it as corruption: check disk health and who else writes into the cache dir"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"fn is_canonical_cached_json(path: &Path, digest: &CacheDigest) -> bool {\n    let Ok(bytes) = std::fs::read(path) else { return false };\n    let Ok(value) = serde_json::from_slice::<serde_json::Value>(&bytes) else { return false };\n    digest.matches_bytes(&bytes).unwrap_or(false)\n        && matches!(canonical_json(&value), Ok(enc) if enc == bytes)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Version remote cache namespaces with the mise version (e.g. 'ci-2026-1') so serializer changes never mix","Never share a local action-cache dir between users or mise versions","Clear caches wholesale after mise upgrades that touch the cache protocol"],"tags":["rust","cache","json","serialization","corruption"],"backgroundTag":"cache-entry-corruption","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}