can1357/oh-my-pi · error · ToolError
Connected to ${cdpUrl} but puppeteer.connect failed: ${(err
Error message
Connected to ${cdpUrl} but puppeteer.connect failed: ${(err as Error).message} What it means
The CDP endpoint answered (waitForCdp succeeded) but puppeteer.connect({browserURL}) failed — e.g. the discovery endpoint returned malformed targets, the browser dropped the connection, or a protocol/timeout error occurred. openBrowserHandle kills the spawned subprocess (if it started one) and wraps the puppeteer error message in ToolError.
Source
Thrown at packages/coding-agent/src/tools/browser/registry.ts:304
} catch (err) {
await gracefulKillTreeOnce(child.pid).catch(() => undefined);
if (err instanceof ToolAbortError) throw err;
if (err instanceof Error && err.name === "AbortError") throw err;
throw new ToolError(`Failed to attach to ${path.basename(exe)} on ${cdpUrl}: ${(err as Error).message}`);
}
}
const puppeteer = await loadPuppeteer();
let browser: Browser;
try {
browser = await puppeteer.connect({
browserURL: cdpUrl,
defaultViewport: null,
protocolTimeout: BROWSER_PROTOCOL_TIMEOUT_MS,
});
} catch (err) {
if (subprocess) await gracefulKillTreeOnce(subprocess.pid);
throw new ToolError(`Connected to ${cdpUrl} but puppeteer.connect failed: ${(err as Error).message}`);
}
return {
key: browserKey(kind),
kind,
browser,
cdpUrl,
pid,
subprocess,
refCount: 0,
stealth: { browserSession: null, override: null },
};
}
export function holdBrowser(handle: BrowserHandle): void {
handle.refCount++;
}
export async function releaseBrowser(handle: BrowserHandle, opts: ReleaseBrowserOptions): Promise<void> {View on GitHub (pinned to 9690622007)
Solutions
- Retry the open — transient protocol timeouts often succeed on a second attempt
- Verify the endpoint really is Chrome CDP: curl http://<cdpUrl>/json/version and check the Browser field
- Update the browser binary or the package so puppeteer and Chrome versions are compatible
- Check for proxies (HTTP_PROXY/HTTPS_PROXY/NO_PROXY) intercepting localhost websockets
Defensive patterns
Strategy: retry
Validate before calling
// preflight: confirm a real Chrome CDP discovery endpoint
const info = await fetch(`${cdpUrl}/json/version`).then(r => r.json()).catch(() => null);
if (!info || !String(info.Browser ?? "").includes("Chrome")) {
console.error(`${cdpUrl} is not a Chrome CDP endpoint`);
} Try / catch
try {
const handle = await openBrowserHandle(kind, { signal });
} catch (err) {
if (err instanceof ToolError && err.message.includes("puppeteer.connect failed")) {
await Bun.sleep(1000); // transient protocol timeouts often clear
return openBrowserHandle(kind, { signal });
}
throw err;
} Prevention
- Exclude localhost from HTTP(S)_PROXY env vars (NO_PROXY=127.0.0.1,localhost)
- Keep puppeteer and the target browser version-compatible
- Don't point browserURL at Electron/other apps with partial CDP support
- Allow generous protocolTimeout for slow machines/remote endpoints
When it happens
Trigger: CDP HTTP endpoint is up but the DevTools websocket handshake fails: mismatched browser version vs bundled puppeteer, endpoint serving a non-Chrome target, flaky network to a remote cdpUrl, or protocolTimeout (BROWSER_PROTOCOL_TIMEOUT_MS) exceeded during connect.
Common situations: Remote debugging over SSH tunnels/containers with dropped connections; Electron-based 'browser' apps with incomplete CDP support; puppeteer version incompatibility with a very new/old Chrome; proxy env vars interfering with the ws upgrade.
Related errors
- Target ${payload.targetId} is no longer available on the att
- Browser is not connected
- Target ${targetId} is no longer available on the attached br
- Timed out waiting for CDP endpoint ${cdpUrl}${lastStatus !==
- No page targets available on the attached browser
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/05453823c32175a7.
Report an issue: GitHub.