jdx/mise · error
remote cache directory is not canonical JSON
Error message
remote cache directory is not canonical JSON
What it means
After downloading a directory proto, mise re-serializes it through its canonical JSON serializer and byte-compares against what was fetched; any difference in key order, whitespace, or encoding bails with 'remote cache directory is not canonical JSON'. Because blobs are content-addressed, this pins the exact byte form mise wrote and catches re-encoded or forged blobs.
Source
Thrown at src/task/task_cache_store.rs:720
mode: u32,
target: PathBuf,
},
}
async fn materialize_remote_tree(
store: &HttpTaskCacheStore,
root: &CacheDigest,
) -> Result<tempfile::NamedTempFile> {
let mut pending = vec![(PathBuf::new(), root.clone(), BTreeSet::new())];
let mut nodes = BTreeMap::<PathBuf, RestoredNode>::new();
while let Some((path, digest, mut ancestors)) = pending.pop() {
if !ancestors.insert(digest.clone()) {
bail!("remote cache directory graph contains a cycle");
}
let bytes = store.client.get_blob(&digest, DIRECTORY_MEDIA_TYPE).await?;
let directory: RemoteDirectory = serde_json::from_slice(&bytes)?;
if canonical_json(&serde_json::to_value(&directory)?)? != bytes {
bail!("remote cache directory is not canonical JSON");
}
if directory.version != 1 {
bail!("unsupported remote cache directory version");
}
let mut names = BTreeSet::new();
for directory in directory.directories {
validate_cache_name(&directory.name)?;
if !names.insert(directory.name.clone()) {
bail!("remote cache directory contains duplicate names");
}
let child = path.join(&directory.name);
validate_cache_path(&child)?;
nodes.insert(
child.clone(),
RestoredNode::Directory {
mode: directory.mode,
},
);View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Delete the non-canonical blobs and let a current mise re-upload them
- Ensure nothing between mise and the storage rewrites request/response bodies
- Pin one mise version for all cache writers and upgrade them together
- If you operate the CAS, byte-validate blobs at upload time against the client's canonical form
Defensive patterns
Strategy: fallback
Try / catch
match restore_from_remote(&key).await {
Ok(hit) => hit,
Err(err) if err.to_string().contains("not canonical JSON") => {
warn!("non-canonical blob at digest; purging and re-uploading");
purge_blobs_for(&key).await.ok();
run_task_uncached_then_upload(&task).await?
}
Err(err) => return Err(err),
} Prevention
- Keep intermediaries (proxies, transforms) from rewriting JSON bodies between mise and storage
- Only mise itself should write blobs; no pretty-printers or re-serializers in the pipeline
- Upgrade all cache-writing machines on the same mise version
- Hash-verify blobs at the storage boundary so re-encodes are caught server-side
When it happens
Trigger: The blob stored at a directory digest was pretty-printed, key-reordered, or re-encoded by an intermediary, or was written by a different mise version whose canonical serialization differs.
Common situations: A proxy between mise and storage that normalizes JSON bodies; custom scripts uploading directory protos; version skew across mise releases changing serde output.
Related errors
- remote action result is missing its output root
- remote cache directory graph contains a cycle
- remote cache directory contains duplicate names
- unsupported remote cache client metadata version
- remote cache metadata kind is not a task
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/a0d8529dab591cca.
Report an issue: GitHub.