denoland/deno · error
upstream /json/list at {host} returned {}
Error message
upstream /json/list at {host} returned {} What it means
The desktop devtools multiplexer fetches `/json/list` over HTTP from the embedded browser's remote-debugging endpoint to discover the page target's webSocketDebuggerUrl, and bails on any non-success status. The upstream debug server either isn't serving yet, is on a different host/port than expected, or rejected the request.
Source
Thrown at cli/tools/desktop_devtools.rs:722
let stream = TcpStream::connect(host).await?;
let io = TokioIo::new(stream);
let (mut sender, conn) = hyper::client::conn::http1::handshake(io)
.await
.map_err(|e| anyhow!("http handshake to {host} failed: {e}"))?;
tokio::spawn(async move {
if let Err(err) = conn.await {
log::trace!("[devtools-mux] upstream conn closed: {err:?}");
}
});
let req = hyper::Request::builder()
.method(http::Method::GET)
.uri("/json/list")
.header(http::header::HOST, host.to_string())
.body(Empty::<Bytes>::new())?;
let resp = sender.send_request(req).await?;
if !resp.status().is_success() {
bail!("upstream /json/list at {host} returned {}", resp.status());
}
let body = resp.collect().await?.to_bytes();
let value: Value = serde_json::from_slice(&body)
.with_context(|| format!("upstream /json/list at {host} not JSON"))?;
let ws_url = value
.as_array()
.and_then(|arr| {
arr.iter().find_map(|v| {
// Skip targets that are our own DevTools frontend window — when
// openDevtools() creates a CEF window pointed at inspector.html,
// CEF registers it as a debuggable target. Connecting to it
// instead of the real app window would show "DevTools for
// DevTools".
let url = v.get("url").and_then(|u| u.as_str()).unwrap_or("");
if url.contains("/devtools/") || url.contains("devtools://") {
return None;
}View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Retry the devtools connection after the app window is fully up — the most common cause is a startup race.
- Curl the endpoint yourself: `curl http://<host>:<port>/json/list` — if that fails, the address is wrong or the server isn't listening.
- Kill stale instances of the app/browser holding the debugging port, then relaunch.
- Verify the remote-debugging port configuration matches what the mux is given, and prefer 127.0.0.1.
Defensive patterns
Strategy: retry
Validate before calling
# Wait until the remote-debugging endpoint answers before opening devtools
for i in $(seq 1 20); do
curl -fsS "http://127.0.0.1:${PORT}/json/list" >/dev/null && break
sleep 0.5
done Try / catch
// Client-side pattern around the mux/devtools attach
async function attachWithRetry(url: string, attempts = 5): Promise<Response> {
for (let i = 0; i < attempts; i++) {
try {
const r = await fetch(`${url}/json/list`);
if (r.ok) return r;
} catch { /* not ready */ }
await new Promise((r) => setTimeout(r, 500 * (i + 1)));
}
throw new Error(`devtools endpoint at ${url} not responding`);
} Prevention
- Give the app a startup grace period before attaching devtools; the debug server races process init.
- Prefer 127.0.0.1 and the exact configured port; avoid port guessing.
- Ensure only one debuggable app instance runs — stale instances hold the endpoint.
When it happens
Trigger: The mux connects before the CEF/browser debug endpoint is ready (startup race), the configured host/port doesn't match the actual remote-debugging address, another process occupies the port, or the browser process died between connection and the HTTP request. Any 4xx/5xx status lands here.
Common situations: Opening devtools immediately at app launch; a stale port left over from a previous crashed run; a second debuggable instance already holding the endpoint; proxies/firewalls interfering with localhost HTTP.
Related errors
- LAUFEY backend exited with status: {}
- Deno.autoUpdate: check failed:
- Either `node` or `range` must be provided when reporting an
- fetch failed
- Invalid port: '${maybePort}'
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/ccd028b545f5e816.
Report an issue: GitHub.