Kuberwastaken/claurst · error
WebSocket connect to
Error message
WebSocket connect to {} failed: {} What it means
Thrown by `connect` when the tungstenite `connect_async` call to Chrome's webSocketDebuggerUrl fails. The HTTP target lookup succeeded, but establishing the CDP WebSocket handshake did not; the underlying tungstenite error is embedded in the message.
Solutions
- Confirm Chrome is still running and the debug port is alive (`curl http://127.0.0.1:<port>/json/version`).
- Re-run `/chrome connect` — the previous ws URL may be stale after a Chrome restart.
- Check firewall/proxy settings that could block the WebSocket upgrade on the debug port.
- Retry with a fresh port if Chrome was relaunched with a different --remote-debugging-port.
Example fix
// before: stale URL after Chrome restart
let (ws, _) = connect_async(&ws_url).await?;
// after: re-fetch target list immediately before dialing
let targets = fetch_targets(port).await?;
let ws_url = pick_page_ws_url(&targets)?;
let (ws, _) = connect_async(&ws_url).await.map_err(|e| anyhow!("WebSocket connect to {} failed: {}", ws_url, e))?; Defensive patterns
Strategy: retry
Validate before calling
let alive = reqwest::get(format!("http://127.0.0.1:{}/json/version", port)).await.is_ok();
if !alive { /* Chrome is down; restart it before connecting */ } Type guard
async fn ws_reachable(ws_url: &str) -> bool {
// cheap check: HTTP endpoint on the same port still answers
ws_url.parse::<reqwest::Url>().is_ok() && port_probe(ws_url).await
} Try / catch
match connect(port).await {
Ok(s) => Ok(s),
Err(e) if e.to_string().contains("WebSocket connect") => {
tokio::time::sleep(Duration::from_millis(500)).await;
connect(port).await // retry once with fresh target list
}
Err(e) => Err(e),
} Prevention
- Fetch the target list immediately before dialing the WebSocket.
- Retry connect once on failure — stale ws URLs are common after Chrome restarts.
- Keep Chrome alive for the whole automation session.
- Check proxy/firewall rules for the debug port.
When it happens
Trigger: Chrome closed between the /json HTTP query and the WebSocket dial; the ws URL host/port unreachable; proxy or TLS interference; Chrome restarted and the tab (and its debugger URL) is gone; the ws_url from a stale target list no longer accepts connections.
Common situations: Race where Chrome is shutting down while connect runs; connecting to a Docker-mapped port where only the HTTP endpoint is forwarded; wrong port collision (another service answering /json-shaped responses).
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
- WebSocket closed unexpectedly
- WebSocket closed by Chrome
- No debuggable page found on port
- CDP error
- No screenshot data in response
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/b41e4724692e00af.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/commands/src/chrome.rs:146
.ok_or_else(|| {
anyhow::anyhow!(
"No debuggable page found on port {}. \
Make sure Chrome has at least one open tab.",
port
)
})?;
let tab_url = tabs
.as_array()
.and_then(|arr| {
arr.iter()
.find(|t| t["type"] == "page")
.and_then(|t| t["url"].as_str().map(|s| s.to_string()))
})
.unwrap_or_default();
let (ws, _) = connect_async(&ws_url).await.map_err(|e| {
anyhow::anyhow!("WebSocket connect to {} failed: {}", ws_url, e)
})?;
let mut session = ChromeSession { ws, port, tab_url: tab_url.clone() };
// Enable Page domain so captureScreenshot etc. work.
cdp_call(&mut session.ws, "Page.enable", json!({})).await?;
// Enable Runtime domain for eval/click/fill.
cdp_call(&mut session.ws, "Runtime.enable", json!({})).await?;
store_session(session);
Ok(format!(
"Connected to Chrome on port {} (tab: {})",
port, tab_url
))
}
/// Disconnect the current session.
pub fn disconnect() -> String {
View on GitHub (pinned to b0637c97ec)