-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Bug Summary
agent-browser --cdp 9222 open <url> kills the external Chrome process instead of gracefully disconnecting when the CDP connection validation fails.
Environment
- agent-browser: 0.14.0
- Chrome Canary: 147.0.7701.0
- macOS, ARM64
- playwright-core (bundled)
Reproduction
- Start Chrome with remote debugging:
/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --remote-debugging-port=9222
- Verify CDP is active:
curl -s http://localhost:9222/json/version # returns valid JSON - Run:
agent-browser --cdp 9222 open https://example.com
- Result: Chrome Canary process is killed.
curl -s http://localhost:9222/json/versionreturns empty.
Contrast: agent-browser --cdp 9222 tab new https://example.com works correctly without killing Chrome.
Root Cause
Two locations in dist/browser.js call browser.close() on a Playwright Browser object obtained via chromium.connectOverCDP(). Playwright's connectOverCDP() does not set _shouldCloseConnectionOnClose = true (unlike connect()), so .close() sends the CDP Browser.close command which terminates the browser process.
Location 1: connectViaCDP error handler (line ~1202-1205)
catch (error) {
// Clean up browser connection if validation or setup failed
await browser.close().catch(() => { }); // <-- kills Chrome!
throw error;
}If anything throws during page validation (e.g., no pages with non-empty URLs, setupPageTracking failure), this catch block runs browser.close() which sends Browser.close via CDP → Chrome dies.
Location 2: BrowserManager.close() (line ~1990-1995)
else if (this.cdpEndpoint !== null) {
// CDP: only disconnect, don't close external app's pages
if (this.browser) {
await this.browser.close().catch(() => { }); // <-- also kills Chrome!
}
}The comment explicitly says "only disconnect, don't close external app's pages" but .close() does the opposite for CDP connections.
Playwright behavior reference
| Connection method | _shouldCloseConnectionOnClose |
.close() behavior |
|---|---|---|
chromium.connect() |
true (browserType.js:145) |
Drops WebSocket only |
chromium.connectOverCDP() |
false (default) |
Sends Browser.close CDP command → kills process |
Suggested Fix
Replace browser.close() with a disconnect-only call at both locations. Options:
-
Preferred: Use
browser.disconnect()if available, or access the underlying connection:// In connectViaCDP catch block: await browser._connection?.close(); // In BrowserManager.close() CDP branch: await browser._connection?.close();
-
Alternative: Set the flag after connecting:
const browser = await chromium.connectOverCDP(cdpUrl, { timeout: options?.timeout }); browser._shouldCloseConnectionOnClose = true; // Ensure close() only disconnects
-
Simplest: Just don't call close in the error handler — let the WebSocket connection be garbage-collected:
catch (error) { // Don't call browser.close() — it kills external Chrome via CDP throw error; }
Why tab new works
tab new <url> runs after connectViaCDP has already succeeded (the try block completed). It creates a new page via context.newPage() + page.goto() — no risk of hitting the catch block with browser.close().