jdx/mise · error

remote cache client metadata is not canonical JSON

Error message

remote cache client metadata is not canonical JSON

What it means

After fetching the client-metadata blob, mise re-serializes the parsed value with its canonical JSON encoder and requires the output to equal the served bytes byte-for-byte (src/task/task_cache_store.rs:405-407). Any difference — key reordering, whitespace, escape style, unicode normalization — means the blob was not stored or served verbatim, which breaks the content-addressed trust model, so the read aborts.

Source

Thrown at src/task/task_cache_store.rs:406

        let action = CacheDigest {
            algorithm: "blake3".into(),
            hash: key.to_string(),
            size: action_size,
        };
        let Some(result) = self.client.get_action_result(&action).await? else {
            return Ok(None);
        };
        let metadata = result
            .metadata
            .as_ref()
            .ok_or_else(|| eyre!("remote action result is missing client metadata"))?;
        let metadata_bytes = self
            .client
            .get_blob(metadata, CLIENT_METADATA_MEDIA_TYPE)
            .await?;
        let metadata: RemoteClientMetadata = serde_json::from_slice(&metadata_bytes)?;
        if canonical_json(&serde_json::to_value(&metadata)?)? != metadata_bytes {
            bail!("remote cache client metadata is not canonical JSON");
        }
        let mut manifest = metadata.into_manifest(key)?;
        for root in &manifest.roots {
            validate_cache_path(root)?;
        }
        let artifact = match &result.output_root {
            Some(root) => {
                let temporary = materialize_remote_tree(self, root).await?;
                Some(TaskCacheStoreArtifact::temporary(temporary))
            }
            None if manifest.roots.is_empty() => None,
            None => bail!("remote action result is missing its output root"),
        };
        manifest.artifact_checksum = Some(calculate_artifact_checksum(
            &manifest,
            artifact.as_ref().map(TaskCacheStoreArtifact::path),
        )?);
        let manifest = serde_json::to_vec(&manifest)?;

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Verify the server serves blob bytes unmodified: compare the blake3 digest of what the client uploaded with what a direct GET returns.
  2. Remove any JSON-transforming proxy/CDN rule (disable content optimization) in front of the remote cache URL, or bypass it.
  3. Delete the non-canonical entries and let a current mise rewrite them canonically.
  4. Until fixed, set task.cache.remote_mode (or MISE_TASK_CACHE_REMOTE_MODE) to write-only so reads never hit the rewriting layer.

Example fix

# before: server re-emits JSON from a DB row (non-canonical)
GET /blobs/<digest>  -> {"version": 1, "kind": "task", ...}   # reordered keys

# after: server stores and returns the exact uploaded bytes
GET /blobs/<digest>  -> exact byte stream, digest matches blake3 of upload
Defensive patterns

Strategy: fallback

Validate before calling

# verify the server serves blobs byte-exact: fetch a blob twice and hash it
curl -s "$REMOTE_URL/blobs/$DIGEST" | b3sum   # must equal the digest it was stored under
curl -s "$REMOTE_URL/blobs/$DIGEST" | b3sum   # stable across reads

Try / catch

# shell: if a rewriting proxy corrupts blobs, drop to write-only and report
if ! mise run build; then
  dmesg_cache_err=$(mise run build 2>&1)
  case "$dmesg_cache_err" in *"not canonical JSON"*)
    echo "remote cache server rewrites JSON — disabling remote reads";
    MISE_TASK_CACHE_REMOTE_MODE=write-only mise run build ;;
  esac
fi

Prevention

When it happens

Trigger: get_action_result returns a metadata digest; get_blob fetches it; canonical_json(serde_json::to_value(metadata)) != metadata_bytes. Happens when a proxy, CDN, or gateway re-serializes JSON responses, when the server regenerates JSON on read instead of serving the stored blob, or when a non-mise writer uploaded pretty-printed/non-canonical JSON.

Common situations: Corporate MITM proxies or CDNs configured to "optimize" JSON; a custom remote-cache server that stores metadata as a database row and re-emits JSON; hand-uploaded debug blobs; charset/encoding transcoding in front of the cache endpoint.

Related errors


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