{"record":{"id":"a0d8529dab591cca","repo":"jdx/mise","slug":"remote-cache-directory-is-not-canonical-json","errorCode":null,"errorMessage":"remote cache directory is not canonical JSON","messagePattern":"remote cache directory is not canonical JSON","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache_store.rs","lineNumber":720,"sourceCode":"        mode: u32,\n        target: PathBuf,\n    },\n}\n\nasync fn materialize_remote_tree(\n    store: &HttpTaskCacheStore,\n    root: &CacheDigest,\n) -> Result<tempfile::NamedTempFile> {\n    let mut pending = vec![(PathBuf::new(), root.clone(), BTreeSet::new())];\n    let mut nodes = BTreeMap::<PathBuf, RestoredNode>::new();\n    while let Some((path, digest, mut ancestors)) = pending.pop() {\n        if !ancestors.insert(digest.clone()) {\n            bail!(\"remote cache directory graph contains a cycle\");\n        }\n        let bytes = store.client.get_blob(&digest, DIRECTORY_MEDIA_TYPE).await?;\n        let directory: RemoteDirectory = serde_json::from_slice(&bytes)?;\n        if canonical_json(&serde_json::to_value(&directory)?)? != bytes {\n            bail!(\"remote cache directory is not canonical JSON\");\n        }\n        if directory.version != 1 {\n            bail!(\"unsupported remote cache directory version\");\n        }\n        let mut names = BTreeSet::new();\n        for directory in directory.directories {\n            validate_cache_name(&directory.name)?;\n            if !names.insert(directory.name.clone()) {\n                bail!(\"remote cache directory contains duplicate names\");\n            }\n            let child = path.join(&directory.name);\n            validate_cache_path(&child)?;\n            nodes.insert(\n                child.clone(),\n                RestoredNode::Directory {\n                    mode: directory.mode,\n                },\n            );","sourceCodeStart":702,"sourceCodeEnd":738,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/task/task_cache_store.rs#L702-L738","documentation":"Remote directory blobs are required to be stored as canonical JSON (keys sorted, no insignificant whitespace). After deserializing and re-serializing the blob, mise compares the canonical form to the raw bytes; a mismatch means the blob was written by a non-conforming producer, so it bails rather than trusting it.","triggerScenarios":"get_blob returns a directory blob whose bytes differ from canonical_json(serde_json::to_value(directory)) — i.e. the blob was serialized with unsorted keys, extra whitespace, or other non-canonical formatting.","commonSituations":"A custom or older cache writer stored pretty-printed or non-deterministically ordered JSON; blobs were transformed/normalized by an intermediary (proxy, CDN) that re-serialized them; manual editing of remote blobs.","solutions":["Re-upload the cache entries using mise itself (re-run the task) so blobs are written with canonical JSON.","Fix the custom cache producer to serialize with mise's canonical JSON routine (sorted keys, compact output).","Purge affected blobs from the remote store and let clients repopulate them.","Check whether any storage layer is re-encoding JSON on write/read and disable that transformation."],"exampleFix":"// before: pretty, non-canonical\nserde_json::to_string_pretty(&directory)\n// after: canonical form as required\nserde_json::to_vec(&directory)","handlingStrategy":"validation","validationCode":"const canonical = o => JSON.stringify(o, Object.keys(o).sort());\nif (canonical(parsed) !== rawString) throw new Error('directory blob is not canonical JSON');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Serialize directory blobs with sorted keys and compact output only.","Disable any intermediary (proxy/storage layer) that re-serializes JSON.","Write blobs via mise itself rather than custom serializers."],"tags":["task-cache","remote-cache","json","canonicalization"],"backgroundTag":"unexpected-response-shape","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}