{"record":{"id":"a289b5863feec021","repo":"can1357/oh-my-pi","slug":"timed-out-waiting-for-cdp-endpoint-cdpurl-last","errorCode":null,"errorMessage":"Timed out waiting for CDP endpoint ${cdpUrl}${lastStatus !== null ? `: HTTP ${lastStatus}` : \"\"}","messagePattern":"Timed out waiting for CDP endpoint (.+?)(.+?)` : \"\"\\}","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/attach.ts","lineNumber":117,"sourceCode":"\t\tfinish(null);\n\t}\n\treturn promise;\n}\n\n/** Poll `${cdpUrl}/json/version` until it responds with 200, with abort + timeout support. */\nexport async function waitForCdp(cdpUrl: string, timeoutMs: number, signal?: AbortSignal): Promise<void> {\n\tconst deadline = Date.now() + timeoutMs;\n\tconst probeUrl = `${cdpUrl.replace(/\\/+$/, \"\")}/json/version`;\n\tlet lastStatus: number | null = null;\n\twhile (Date.now() < deadline) {\n\t\tthrowIfAborted(signal);\n\t\tconst status = await probeCdpStatus(probeUrl, { timeoutMs: 2000, signal });\n\t\tif (status !== null && status >= 200 && status < 300) return;\n\t\tlastStatus = status;\n\t\tawait Bun.sleep(150);\n\t}\n\tthrowIfAborted(signal);\n\tthrow new ToolError(\n\t\t`Timed out waiting for CDP endpoint ${cdpUrl}${lastStatus !== null ? `: HTTP ${lastStatus}` : \"\"}`,\n\t);\n}\n\n/**\n * Pull a `--remote-debugging-port=<n>` value out of an argv array (Chromium\n * accepts both `--flag=value` and `--flag value`). Returns null if absent or\n * malformed.\n */\nfunction findCdpPortInArgs(args: string[]): number | null {\n\tfor (const arg of args) {\n\t\tconst m = /^--remote-debugging-port=(\\d+)$/.exec(arg);\n\t\tif (m) {\n\t\t\tconst port = Number.parseInt(m[1]!, 10);\n\t\t\tif (Number.isFinite(port) && port > 0) return port;\n\t\t}\n\t}\n\tfor (let i = 0; i < args.length - 1; i++) {","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/attach.ts#L99-L135","documentation":"waitForCdp polls a Chromium DevTools Protocol HTTP endpoint (e.g. http://127.0.0.1:9222/json/version) until it returns a 2xx status. If the endpoint never becomes healthy before the wait budget expires, it throws ToolError with the CDP URL and the last HTTP status observed (if any probe actually answered). This guards against attaching to a browser that never exposed its debugging port.","triggerScenarios":"Attaching to a Chromium instance whose --remote-debugging-port never opened; browser process crashed during startup; wrong port/host in the CDP URL; devtools server bound to a different interface (e.g. only IPv6 or a non-localhost address); proxy env vars intercepting localhost requests.","commonSituations":"Launching Electron/Chrome with a bad or already-used debugging port; container networking hiding the port from the host; slow machine where the browser takes longer than the wait window; NO_PROXY not set so HTTP_PROXY breaks the 127.0.0.1 probe (HTTP 4xx/5xx in the message).","solutions":["Confirm the browser process is running and the port matches --remote-debugging-port","Probe the URL manually (curl http://127.0.0.1:<port>/json/version) to see the actual response","Check the browser's stderr/stdout for crash or port-binding errors","Ensure proxy env vars exclude localhost (NO_PROXY=127.0.0.1,localhost)","Increase the wait budget or retry the attach if the machine is slow to start the browser"],"exampleFix":"// before\nawait openBrowserHandle({ cdpUrl: \"http://127.0.0.1:9222\" }); // browser slow to bind\n// after\n// 1. launch with an explicit free port\n// chrome --remote-debugging-port=9222\n// 2. verify before attaching:\nconst ok = await fetch(\"http://127.0.0.1:9222/json/version\").then(r => r.ok).catch(() => false);\nif (!ok) throw new Error(\"CDP endpoint not ready\");\nawait openBrowserHandle({ cdpUrl: \"http://127.0.0.1:9222\" });","handlingStrategy":"retry","validationCode":"async function cdpReady(url: string, timeoutMs = 10000): Promise<boolean> {\n  const end = Date.now() + timeoutMs;\n  while (Date.now() < end) {\n    const ok = await fetch(`${url}/json/version`).then(r => r.ok).catch(() => false);\n    if (ok) return true;\n    await Bun.sleep(250);\n  }\n  return false;\n}\nif (!(await cdpReady(cdpUrl))) throw new Error(\"CDP endpoint never became healthy\");","typeGuard":null,"tryCatchPattern":"try {\n  await waitForCdp(cdpUrl, { signal });\n} catch (e) {\n  if (e instanceof ToolError && e.message.includes(\"Timed out waiting for CDP endpoint\")) {\n    // inspect lastStatus in message; restart browser with a fresh port and retry\n  } else throw e;\n}","preventionTips":["Pick a free port and pass it explicitly to --remote-debugging-port","Set NO_PROXY=127.0.0.1,localhost so probes are not proxied","Check browser stderr for startup crashes before attaching","Wait/poll /json/version yourself before calling attach"],"tags":["browser","cdp","network","timeout"],"backgroundTag":"cdp-endpoint-unreachable","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}