jdx/mise · error · eyre::Report

remote action manifest ETag does not match its body

Error message

remote action manifest ETag does not match its body

What it means

get_action_manifest requires the response to carry a strong quoted ETag equal to the blake3 hex digest of the exact body bytes. After downloading, the client recomputes blake3 over the body and compares; a mismatch means the body or the ETag was altered between server and client, or the server does not implement the contract. This check protects the optimistic-concurrency (If-Match) manifest updates from silent corruption.

Source

Thrown at crates/mise-cache-core/src/lib.rs:425

        let url = self.action_manifest_endpoint(key)?;
        retry_async("GET", &url, self.retries, || async {
            let response = self
                .request(
                    reqwest::Method::GET,
                    url.clone(),
                    TASK_ACTION_MANIFEST_MEDIA_TYPE,
                )
                .await?
                .send()
                .await?;
            if response.status() == StatusCode::NOT_FOUND {
                return Ok(None);
            }
            let response = response.error_for_status()?;
            let etag = parse_strong_etag(response.headers().get(ETAG))?;
            let bytes = response.bytes().await?.to_vec();
            if blake3::hash(&bytes).to_hex().as_str() != etag {
                bail!("remote action manifest ETag does not match its body");
            }
            Ok(Some(RemoteActionManifest { bytes, etag }))
        })
        .await
    }

    pub async fn put_action_manifest(
        &self,
        key: &CacheDigest,
        bytes: &[u8],
        expected_etag: Option<&str>,
    ) -> Result<ManifestPutOutcome> {
        let url = self.action_manifest_endpoint(key)?;
        let body = bytes.to_vec();
        let expected_etag = expected_etag.map(quoted_etag).transpose()?;
        retry_async("PUT", &url, self.retries, || async {
            let mut request = self
                .request(

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Disable body transformation (gzip/transcoding) for the manifest media type on any proxy or CDN in front of the cache
  2. Fix the server to set the ETag to the quoted blake3 hex digest of the exact bytes it serves
  3. Purge the affected manifest entries on the server/CDN once corruption is confirmed
  4. Treat a manifest fetch that fails this check as absent (None) and rebuild the manifest from local state

Example fix

# before: proxy rewrites the manifest body (gzip) but forwards the original ETag
location /v1/action-manifests/ {
    gzip on;  # body no longer hashes to the ETag -> client rejects it
}

# after: serve manifest bytes untransformed so they hash to their ETag
location /v1/action-manifests/ {
    gzip off;
    proxy_set_header Accept-Encoding "";
}
Defensive patterns

Strategy: fallback

Try / catch

let manifest = match client.get_action_manifest(&key).await {
    Ok(manifest) => manifest,
    Err(report) if report.to_string().contains("ETag does not match its body") => {
        // body/etag corruption in transit: treat as absent and rebuild the manifest
        None
    }
    Err(report) => return Err(report),
};

Prevention

When it happens

Trigger: An intermediary proxy that transcodes the body (gzip, charset re-encoding) while forwarding the original ETag; a server that computes the ETag with sha256 or over different bytes; a CDN serving a stale body with a newer ETag; deliberate cache poisoning.

Common situations: Corporate proxies or CDNs modifying response bodies; a custom cache server implemented against an older or incorrect spec; compression enabled at the proxy without recomputing ETags for the application/vnd.mise.cache-task-action-manifest.v1+json media type.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/3434eb3c06a8a42e. Report an issue: GitHub.