jdx/mise · error · eyre::Report
task action manifest is not canonical JSON
Error message
task action manifest is not canonical JSON
What it means
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.
Source
Thrown at crates/mise-cache-core/src/agent.rs:593
fn load_task_manifest(&self, task: &str) -> Result<Option<TaskActionManifest>> {
match fs::read(self.task_manifest_path(task)) {
Ok(contents) => Ok(Some(self.parse_task_manifest(task, &contents, false)?)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(error.into()),
}
}
fn parse_task_manifest(
&self,
task: &str,
contents: &[u8],
require_canonical: bool,
) -> Result<TaskActionManifest> {
let manifest: TaskActionManifest = serde_json::from_slice(contents)?;
validate_task_manifest(&manifest, task)?;
if require_canonical && canonical_json(&manifest)? != contents {
bail!("task action manifest is not canonical JSON");
}
Ok(manifest)
}
fn task_manifest_selector(task: &str) -> Result<(Vec<u8>, CacheDigest)> {
let bytes = canonical_json(&TaskActionManifestSelector {
version: 1,
kind: "task_action_manifest",
task,
})?;
let digest = CacheDigest::blake3(&bytes);
Ok((bytes, digest))
}
fn persist_task_manifest(&self, manifest: &TaskActionManifest) -> Result<()> {
let bytes = canonical_json(manifest)?;
fs::create_dir_all(self.manifest_dir.as_path())?;
let mut temporary = tempfile::NamedTempFile::new_in(self.manifest_dir.as_path())?;View on GitHub (pinned to 6f52dcdf99)
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
Defensive patterns
Strategy: fallback
Try / catch
// Treat non-canonical manifests as cache misses: regenerate rather than trust
match agent.begin_task(&task).await {
Ok(run) => run,
Err(e) if e.to_string().contains("not canonical JSON") => {
remove_task_manifest(&task)?; // drop the mutated file
agent.begin_task(&task).await?
}
Err(e) => return Err(e),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- remote action output directory is invalid
- remote action descriptor is missing
- remote action metadata is missing
- remote action output file is missing
- remote cache client metadata is not canonical JSON
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/17c88d793ff192b9.
Report an issue: GitHub.