{"record":{"id":"359c6fd10a752e46","repo":"jdx/mise","slug":"remote-cache-client-metadata-is-not-canonical-json","errorCode":null,"errorMessage":"remote cache client metadata is not canonical JSON","messagePattern":"remote cache client metadata is not canonical JSON","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache_store.rs","lineNumber":406,"sourceCode":"        let action = CacheDigest {\n            algorithm: \"blake3\".into(),\n            hash: key.to_string(),\n            size: action_size,\n        };\n        let Some(result) = self.client.get_action_result(&action).await? else {\n            return Ok(None);\n        };\n        let metadata = result\n            .metadata\n            .as_ref()\n            .ok_or_else(|| eyre!(\"remote action result is missing client metadata\"))?;\n        let metadata_bytes = self\n            .client\n            .get_blob(metadata, CLIENT_METADATA_MEDIA_TYPE)\n            .await?;\n        let metadata: RemoteClientMetadata = serde_json::from_slice(&metadata_bytes)?;\n        if canonical_json(&serde_json::to_value(&metadata)?)? != metadata_bytes {\n            bail!(\"remote cache client metadata is not canonical JSON\");\n        }\n        let mut manifest = metadata.into_manifest(key)?;\n        for root in &manifest.roots {\n            validate_cache_path(root)?;\n        }\n        let artifact = match &result.output_root {\n            Some(root) => {\n                let temporary = materialize_remote_tree(self, root).await?;\n                Some(TaskCacheStoreArtifact::temporary(temporary))\n            }\n            None if manifest.roots.is_empty() => None,\n            None => bail!(\"remote action result is missing its output root\"),\n        };\n        manifest.artifact_checksum = Some(calculate_artifact_checksum(\n            &manifest,\n            artifact.as_ref().map(TaskCacheStoreArtifact::path),\n        )?);\n        let manifest = serde_json::to_vec(&manifest)?;","sourceCodeStart":388,"sourceCodeEnd":424,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/task/task_cache_store.rs#L388-L424","documentation":"Remote client metadata blobs must be stored as canonical JSON (deterministic key order/encoding). After deserializing, get() re-serializes the metadata canonically and compares bytes; any difference means the blob wasn't written in canonical form, so mise rejects it to guarantee content addressing integrity.","triggerScenarios":"Reading a remote cache entry where the metadata blob bytes differ from the canonical re-encoding of their parsed content — e.g. metadata uploaded by non-mise tooling, pretty-printed JSON, or re-encoded by a proxy/CDN transformation.","commonSituations":"Custom cache upload scripts writing JSON via a generic encoder; intermediaries that reformat or recompress blobs; hand-crafted cache entries in a shared bucket.","solutions":["Upload the metadata blob exactly as mise produced it (raw bytes, no re-encoding)","Delete the non-canonical remote entry and let mise republish it by re-running the task","Bypass any proxy/middleware that rewrites response bodies for the cache storage"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# verify uploaded blob is byte-identical to what mise wrote\ncmp mise_metadata.json uploaded_blob.json","typeGuard":null,"tryCatchPattern":"match cache_get(key) {\n    Err(e) if e.to_string().contains(\"canonical JSON\") => {\n        // republish: delete blob and rerun the task\n        purge_key(key);\n        rerun_task();\n    }\n    r => r?,\n}","preventionTips":["Upload cache blobs verbatim; never round-trip them through a JSON re-encoder","Disable body-transforming proxies/CDN features on cache storage","Only let mise itself write remote cache metadata"],"tags":["remote-cache","json","content-addressing"],"backgroundTag":"invalid-json-response","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"}