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

  1. Run the executable manually with the same flags to see why it exits (crash logs, missing --no-sandbox in root/containers)
  2. Try a different remote debugging port (the port may be occupied by a stale process)
  3. Update or reinstall the browser binary; verify it supports the debugging flags used
  4. 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

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


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