jdx/mise · error · eyre::Report

cached {description} is not canonical JSON

Error message

cached {description} is not canonical JSON

What it means

read_canonical_blob() enforces structural integrity on cached JSON blobs: it parses the bytes, re-serializes with canonical_json, and requires byte equality (src/cache/rustc.rs:516-521). The blob passed the blake3 digest check but is not the canonical encoding, so it was produced by a different serializer version or otherwise diverges from what this build would have written; the entry is treated as untrusted and rejected. It applies to the RustcMetadata and CacheDirectory descriptors read during restore_result (src/cache/rustc.rs:310-316).

Source

Thrown at src/cache/rustc.rs:520

        AgentResponse::Blob { path: Some(path) } if digests.len() == 1 => Ok(vec![path]),
        AgentResponse::Blob { path: None } if digests.len() == 1 => {
            let digest = &digests[0];
            bail!("cached rustc action is missing blob {}", digest.hash)
        }
        AgentResponse::Error { message } => bail!(message),
        _ => bail!("cache agent returned an unexpected blob lookup response"),
    }
}

fn read_canonical_blob<T>(path: &Path, digest: &CacheDigest, description: &str) -> Result<T>
where
    T: DeserializeOwned + Serialize,
{
    let bytes = read_verified_blob(path, digest, description)?;
    let value = serde_json::from_slice(&bytes)
        .wrap_err_with(|| format!("cached {description} is not valid JSON"))?;
    if canonical_json(&value)? != bytes {
        bail!("cached {description} is not canonical JSON");
    }
    Ok(value)
}

fn read_verified_blob(path: &Path, digest: &CacheDigest, description: &str) -> Result<Vec<u8>> {
    let mut bytes = Vec::new();
    std::fs::File::open(path)?.read_to_end(&mut bytes)?;
    if !digest.matches_bytes(&bytes)? {
        bail!("cached {description} failed digest verification");
    }
    Ok(bytes)
}

fn validated_outputs(
    directory: CacheDirectory,
    outputs: &RustcOutputs,
) -> Result<Vec<(CacheFileNode, PathBuf)>> {
    if directory.version != 1 || !directory.directories.is_empty() || !directory.symlinks.is_empty()

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Clear the affected cache (local directory and, if you control it, the remote namespace) and let it repopulate from a single mise version
  2. Pin one mise version per shared cache/namespace, or version the namespace string with the mise version
  3. If it appears without any version change, treat it as corruption: check disk health and who else writes into the cache dir
Defensive patterns

Strategy: fallback

Validate before calling

fn is_canonical_cached_json(path: &Path, digest: &CacheDigest) -> bool {
    let Ok(bytes) = std::fs::read(path) else { return false };
    let Ok(value) = serde_json::from_slice::<serde_json::Value>(&bytes) else { return false };
    digest.matches_bytes(&bytes).unwrap_or(false)
        && matches!(canonical_json(&value), Ok(enc) if enc == bytes)
}

Prevention

When it happens

Trigger: Restoring a metadata or output-directory blob written by a mise version whose canonical JSON encoding (field order, escaping, number formatting) differs; a remote cache namespace populated by clients on a different mise version; cache files rewritten in place by external tooling while the digest list still matches by coincidence of write-order.

Common situations: Sharing one cache directory or remote namespace across mise upgrades; CI caches populated by an older image; hand-edited cache files.

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/d81a448bc6ac08b5. Report an issue: GitHub.