denoland/deno · error

{}: {}

Error message

{}: {}

What it means

This is the fallback branch that converts a failed registry HTTP response into an error: it first tries to decode the body as JSR's `ApiError` JSON (rendered as '<message> (<code>)'); when the body is not that JSON, it bails with '<status>: <snippet>' so a non-JSON response (an HTML error page, an empty 502) stays diagnosable instead of surfacing as an opaque deserialization error.

Source

Thrown at cli/tools/publish/mod.rs:1435

  // Carried into the error so that a report of this warning is traceable in the
  // registry's own logs, which is where the reason for a rejection lives.
  let x_deno_ray = response
    .headers()
    .get("x-deno-ray")
    .and_then(|value| value.to_str().ok())
    .map(|s| s.to_string());

  // The endpoint answers 204 with an empty body on success, so there is nothing
  // to deserialize; on failure the body is the registry's JSON error, which
  // `ApiError` renders as "<message> (<code>)".
  let body = response.collect().await?.to_bytes();
  match serde_json::from_slice::<registry::ApiError>(&body) {
    Ok(mut err) => {
      err.x_deno_ray = x_deno_ray;
      Err(err.into())
    }
    Err(_) => bail!("{}: {}", status, response_body_snippet(&body)),
  }
}

/// Returns a truncated, lossy UTF-8 rendering of a response body for use in
/// error messages, so that a non-JSON response (e.g. an HTML error page) is
/// diagnosable instead of surfacing as an opaque deserialization error.
fn response_body_snippet(bytes: &[u8]) -> String {
  const MAX_LEN: usize = 512;
  let text = String::from_utf8_lossy(bytes);
  let text = text.trim();
  if text.len() > MAX_LEN {
    let mut end = MAX_LEN;
    while !text.is_char_boundary(end) {
      end -= 1;
    }
    format!("{}... (truncated)", &text[..end])
  } else {
    text.to_string()

View on GitHub (pinned to f7822238ca)

Solutions

  1. Retry with backoff — non-JSON error bodies almost always come from transient intermediaries.
  2. If a proxy intercepts traffic, bypass it or allowlist api.jsr.io / jsr.io.
  3. Use the status to triage: 5xx → wait and retry; 4xx → inspect the snippet to see which hop rejected the request.
Defensive patterns

Strategy: retry

Try / catch

#!/usr/bin/env bash
for attempt in 1 2 3; do
  out="$(deno publish 2>&1)" && exit 0
  # non-JSON error bodies (HTML proxy/CDN pages) are almost always transient
  if printf '%s' "$out" | grep -Eq '50[0234]: '; then
    sleep $((attempt * 20)); continue
  fi
  printf '%s\n' "$out" >&2; exit 1
done
exit 1

Prevention

When it happens

Trigger: A JSR API request (publish, package creation, etc.) returns an error status whose body is not the expected ApiError JSON — gateway 502/504 HTML pages, Cloudflare interstitials, corporate proxies rewriting responses, empty bodies.

Common situations: Publishing behind corporate proxies or self-hosted runners with egress filtering; transient CDN errors during releases; JSR incidents.

Related errors


AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20). Data as JSON: /api/errors/7397b824a9600f19. Report an issue: GitHub.