can1357/oh-my-pi · error · ToolError
Failed to attach to ${path.basename(exe)} on ${cdpUrl}: ${(e
Error message
Failed to attach to ${path.basename(exe)} on ${cdpUrl}: ${(err as Error).message} What it means
After launching the browser subprocess with remote debugging, openBrowserHandle waits up to 30 seconds for the CDP endpoint to come up. If waitForCdp fails (browser exited, crashed, never opened the port, or the signal aborted) it kills the child process tree and rethrows as ToolError, wrapping the underlying waitForCdp message plus the executable name and CDP URL.
Source
Thrown at packages/coding-agent/src/tools/browser/registry.ts:290
if (killed > 0) logger.debug("Killed existing instances before attach", { exe, killed });
const port = await findFreeCdpPort();
const launchArgs = [...(opts.appArgs ?? []), `--remote-debugging-port=${port}`];
const child = Bun.spawn([exe, ...launchArgs], {
stdout: "ignore",
stderr: "ignore",
stdin: "ignore",
});
child.unref();
subprocess = child;
pid = child.pid;
cdpUrl = `http://127.0.0.1:${port}`;
try {
await waitForCdp(cdpUrl, 30_000, opts.signal);
} 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,View on GitHub (pinned to 9690622007)
Solutions
- Run the executable manually with the same flags to see why it exits (crash logs, missing --no-sandbox in root/containers)
- Try a different remote debugging port (the port may be occupied by a stale process)
- Update or reinstall the browser binary; verify it supports the debugging flags used
- Check the abort signal isn't cancelling during startup if errors mention AbortError
Defensive patterns
Strategy: retry
Validate before calling
// preflight: can this binary even start? (spawn it with --version)
const probe = Bun.spawnSync([exe, "--version"], { stdout: "pipe", stderr: "pipe" });
if (probe.exitCode !== 0) console.error("Browser binary fails to launch:", probe.stderr.toString()); Try / catch
try {
const handle = await openBrowserHandle(kind, { signal });
} catch (err) {
if (err instanceof ToolError && err.message.startsWith("Failed to attach to")) {
// retry once after killing stale processes on the debug port
}
throw err;
} Prevention
- Verify the browser binary launches manually with the same flags before automating it
- In containers/root, ensure sandbox flags (e.g. --no-sandbox equivalents) are handled
- Pick a free remote-debugging port; clean up stale debug ports between runs
- Keep browser binaries updated; ancient versions may reject current CDP flags
When it happens
Trigger: Launching a browser binary that crashes on startup (incompatible flags, corrupted profile, sandbox issues in containers); a binary that doesn't honor --remote-debugging-port; port conflicts; abort signal firing during the 30s attach wait.
Common situations: Running Chrome/Chromium in Docker without --no-sandbox equivalents the tool expects; old browser versions rejecting new flags; headless environments lacking display libraries; the chosen debug port already in use by another process.
Related errors
- Timed out waiting for CDP endpoint ${cdpUrl}${lastStatus !==
- autoStarted ? `omp browser relay is serving at ${cdpUrl} but
- Command timed out after ${err.message.slice("timeout:".lengt
- Failed to restart ${serverName}: server process did not exit
- Command timed out
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/0b1bae3d101762a7.
Report an issue: GitHub.