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
- Retry with backoff — non-JSON error bodies almost always come from transient intermediaries.
- If a proxy intercepts traffic, bypass it or allowlist api.jsr.io / jsr.io.
- 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
- Allowlist api.jsr.io and jsr.io on corporate proxies/self-hosted runners so error pages are not injected.
- Retry releases with backoff when a status:snippet error shows gateway codes or HTML.
- Log the snippet verbatim in CI — it identifies which hop (proxy, CDN, registry) produced the failure.
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
- Either `node` or `range` must be provided when reporting an
- Return value from serve handler must not be an error respons
- Invalid Header: 'upgrade' header must contain 'websocket'
- Invalid Header: 'connection' header must contain 'Upgrade'
- ERR_INVALID_URL
AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20).
Data as JSON: /api/errors/7397b824a9600f19.
Report an issue: GitHub.