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

  1. Retry the open — transient protocol timeouts often succeed on a second attempt
  2. Verify the endpoint really is Chrome CDP: curl http://<cdpUrl>/json/version and check the Browser field
  3. Update the browser binary or the package so puppeteer and Chrome versions are compatible
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/05453823c32175a7. Report an issue: GitHub.