jdx/mise · error

remote action manifest ETag does not match its body

Error message

remote action manifest ETag does not match its body

What it means

Thrown on action-manifest GET (crates/mise-cache-core/src/lib.rs:723): the response must carry a strong ETag, and blake3 of the response body (hex) must equal that ETag string (parse_strong_etag extracts it). If hashing the body does not reproduce the ETag, the manifest body and its validator disagree — the client refuses to trust the bytes.

Solutions

  1. If you operate the server, set ETag to the strong blake3 hex of the exact bytes written: format!("\"{}\"", blake3::hash(&bytes).to_hex())
  2. Ensure no intermediary recompresses or rewrites manifest responses for the action-manifests routes
  3. If using a generic HTTP framework's auto-ETag, disable it and compute the blake3 ETag explicitly
  4. Retry once to rule out an in-flight body mutation race, then report to the server operator
Defensive patterns

Strategy: try-catch

Validate before calling

// server-side helper to guarantee agreement:
fn manifest_response(bytes: &[u8]) -> Response {
    let etag = format!("\"{}\"", blake3::hash(bytes).to_hex());
    Response::new(bytes).with_header(ETAG, etag)
}

Try / catch

match client.get_action_manifest(&key).await {
    Err(e) if e.to_string().contains("ETag does not match") => {
        // body untrusted: treat as miss and re-upload the manifest
        client.put_action_manifest(&key, &local_bytes, None).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling get_action_manifest where the server's ETag header is not the blake3 hex of the exact body — e.g. the server computes the ETag before final serialization, re-serializes differently (key order, whitespace), uses a weak ETag or an md5-based one, or a proxy modifies the body (recompression) after the ETag was generated.

Common situations: Custom servers deriving ETags from mtime or len rather than content; serde/JSON re-serialization changing byte layout between ETag computation and write; compression in transit; server frameworks that append a suffix to ETags.

Related errors


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

Appendix: source

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

        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 6f52dcdf99)