{"record":{"id":"17c88d793ff192b9","repo":"jdx/mise","slug":"task-action-manifest-is-not-canonical-json","errorCode":null,"errorMessage":"task action manifest is not canonical JSON","messagePattern":"task action manifest is not canonical JSON","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/agent.rs","lineNumber":593,"sourceCode":"\n    fn load_task_manifest(&self, task: &str) -> Result<Option<TaskActionManifest>> {\n        match fs::read(self.task_manifest_path(task)) {\n            Ok(contents) => Ok(Some(self.parse_task_manifest(task, &contents, false)?)),\n            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),\n            Err(error) => Err(error.into()),\n        }\n    }\n\n    fn parse_task_manifest(\n        &self,\n        task: &str,\n        contents: &[u8],\n        require_canonical: bool,\n    ) -> Result<TaskActionManifest> {\n        let manifest: TaskActionManifest = serde_json::from_slice(contents)?;\n        validate_task_manifest(&manifest, task)?;\n        if require_canonical && canonical_json(&manifest)? != contents {\n            bail!(\"task action manifest is not canonical JSON\");\n        }\n        Ok(manifest)\n    }\n\n    fn task_manifest_selector(task: &str) -> Result<(Vec<u8>, CacheDigest)> {\n        let bytes = canonical_json(&TaskActionManifestSelector {\n            version: 1,\n            kind: \"task_action_manifest\",\n            task,\n        })?;\n        let digest = CacheDigest::blake3(&bytes);\n        Ok((bytes, digest))\n    }\n\n    fn persist_task_manifest(&self, manifest: &TaskActionManifest) -> Result<()> {\n        let bytes = canonical_json(manifest)?;\n        fs::create_dir_all(self.manifest_dir.as_path())?;\n        let mut temporary = tempfile::NamedTempFile::new_in(self.manifest_dir.as_path())?;","sourceCodeStart":575,"sourceCodeEnd":611,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/crates/mise-cache-core/src/agent.rs#L575-L611","documentation":"parse_task_manifest deserializes a TaskActionManifest and, when require_canonical=true (remote fetches and persisted local copies), requires the raw bytes to byte-equal canonical_json(&manifest) — compact, deterministically key-ordered JSON. Any drift from that canonical serialization is rejected as tampering or writer skew.","triggerScenarios":"get_remote_task_manifest receiving bytes produced by a writer that used pretty-printing, unsorted maps, or an extra newline; hand-editing the local manifest file; a different serializer version reordering fields.","commonSituations":"Running jq/prettier or an editor that 'fixes' cache JSON files; version skew between writer and reader agents; a shared remote bucket written by tooling with different serialization.","solutions":["Regenerate the manifest instead of editing it: delete the local (or remote) copy and let the next task run repopulate it","If you must write manifests programmatically, serialize with sorted keys and compact formatting to match canonical_json","Upgrade writer and reader to the same mise-cache-core version so serialization matches"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Treat non-canonical manifests as cache misses: regenerate rather than trust\nmatch agent.begin_task(&task).await {\n    Ok(run) => run,\n    Err(e) if e.to_string().contains(\"not canonical JSON\") => {\n        remove_task_manifest(&task)?; // drop the mutated file\n        agent.begin_task(&task).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never run formatters (jq, prettier) or editors with format-on-save over cache files","Write manifests only through the agent's own persist path","Keep writer and reader agent versions aligned when sharing a remote store"],"tags":["mise-cache","json","canonicalization","integrity"],"backgroundTag":"non-canonical-json-mismatch","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}