Environment
- macOS 15 (Darwin 25.4), browser-harness 0.1.0 (git, current main @ 6d20866)
- Primary browser: Dia (Chromium-based), remote debugging enabled via the
chrome://inspect/#remote-debugging checkbox, listening on 127.0.0.1:9222. Note: with the new consent model, /json/version on that port returns 404, and a per-connection "Allow remote debugging" dialog is shown in the browser.
- Google Chrome is installed but was not running. A stale
DevToolsActivePort file (pointing at a dead ephemeral port) was left behind in ~/Library/Application Support/Google/Chrome from an earlier Chrome session that had the inspect checkbox ticked.
TL;DR
On a machine where the user's daily browser is a non-Chrome Chromium (Dia, Arc, Edge, ...) but Chrome is also installed, the harness (1) lets a stale Chrome DevToolsActivePort shadow the healthy target browser during discovery, and (2) on failure force-opens Google Chrome specifically. Combined effect in our case: the agent ended up silently automating a freshly-launched Chrome (without the user's logins) instead of the user's actual browser, and Chrome kept popping up on chrome://inspect/#remote-debugging during the task.
What happened (real-world sequence)
- Agent runs a task. Dia is healthy on 9222 with a valid
DevToolsActivePort in its user-data dir.
get_ws_url() (daemon.py) walks PROFILES in order. Chrome's dir ranks above Dia's, so the stale Chrome file is matched first. The code then polls the dead port for 30s and raises ("DevTools is not live yet") — it never advances to Dia's entry later in PROFILES.
ensure_daemon() matches the error via _needs_chrome_remote_debugging_prompt() and calls _open_chrome_inspect(), which on macOS runs tell application "Google Chrome" ... — hardcoded. Wrong browser for Dia/Arc/Edge users; the user watches Chrome open on the inspect page repeatedly (once per cold harness invocation).
- Because Chrome's inspect checkbox happened to still be ticked, the freshly launched Chrome started a debug server and wrote a new, live
DevToolsActivePort. From that moment Chrome wins every discovery, and the retry connects to Chrome. The task "succeeded" — in the wrong browser, without the user's logins.
Bug 1: discovery stops at the first DevToolsActivePort, even if its port is dead
In get_ws_url(), the PROFILES loop raises after the 30s poll on the first file found instead of moving on to the next profile. A leftover file from any higher-ranked browser permanently shadows a healthy lower-ranked one. (Chromium does not clean up DevToolsActivePort on browser quit, so stale files are the norm, not the exception.)
Suggested fix: quick-probe each candidate (is anything listening on that port?) and skip dead ones, preferring a live endpoint over list order; only fall into the 30s wait/raise when no candidate is live.
Bug 2: _open_chrome_inspect() hardcodes Google Chrome on macOS
For users whose browser is Dia/Arc/Edge, the consent checkbox lives in their browser, so opening Google Chrome is both ineffective and harmful (see step 4 above: it can mint a fresh live debug endpoint in the wrong browser). Related but distinct from #290 (Windows: webbrowser.open() can't handle chrome://).
Suggested fix: open the inspect page in the browser owning the profile dir that discovery matched (or at minimum the system default browser), instead of hardcoded Chrome.
Bug 3 (minor): _is_local_chrome_mode() ignores BU_CDP_URL
It only checks BU_CDP_WS. A user who explicitly pins an endpoint via BU_CDP_URL still gets the chrome://inspect popup flow when the connection fails (e.g., while the per-connection "Allow remote debugging" consent dialog is pending in the target browser and the WS handshake times out).
Suggested fix: treat an explicit BU_CDP_URL like BU_CDP_WS — a user pinning an endpoint doesn't want the Chrome UI recovery flow.
Workaround we use now
A pre-flight script reads the target browser's DevToolsActivePort and writes BU_CDP_WS=ws://127.0.0.1:9222<path> into the repo .env. That bypasses discovery entirely and (because BU_CDP_WS is set) disables the Chrome recovery flow. Works reliably, including across browser restarts (script refreshes the UUID and restarts the daemon when it changes).
Happy to send a PR for any/all of the three fixes if you're open to it.
Environment
chrome://inspect/#remote-debuggingcheckbox, listening on127.0.0.1:9222. Note: with the new consent model,/json/versionon that port returns 404, and a per-connection "Allow remote debugging" dialog is shown in the browser.DevToolsActivePortfile (pointing at a dead ephemeral port) was left behind in~/Library/Application Support/Google/Chromefrom an earlier Chrome session that had the inspect checkbox ticked.TL;DR
On a machine where the user's daily browser is a non-Chrome Chromium (Dia, Arc, Edge, ...) but Chrome is also installed, the harness (1) lets a stale Chrome
DevToolsActivePortshadow the healthy target browser during discovery, and (2) on failure force-opens Google Chrome specifically. Combined effect in our case: the agent ended up silently automating a freshly-launched Chrome (without the user's logins) instead of the user's actual browser, and Chrome kept popping up onchrome://inspect/#remote-debuggingduring the task.What happened (real-world sequence)
DevToolsActivePortin its user-data dir.get_ws_url()(daemon.py) walksPROFILESin order. Chrome's dir ranks above Dia's, so the stale Chrome file is matched first. The code then polls the dead port for 30s and raises ("DevTools is not live yet") — it never advances to Dia's entry later inPROFILES.ensure_daemon()matches the error via_needs_chrome_remote_debugging_prompt()and calls_open_chrome_inspect(), which on macOS runstell application "Google Chrome" ...— hardcoded. Wrong browser for Dia/Arc/Edge users; the user watches Chrome open on the inspect page repeatedly (once per cold harness invocation).DevToolsActivePort. From that moment Chrome wins every discovery, and the retry connects to Chrome. The task "succeeded" — in the wrong browser, without the user's logins.Bug 1: discovery stops at the first
DevToolsActivePort, even if its port is deadIn
get_ws_url(), thePROFILESloop raises after the 30s poll on the first file found instead of moving on to the next profile. A leftover file from any higher-ranked browser permanently shadows a healthy lower-ranked one. (Chromium does not clean upDevToolsActivePorton browser quit, so stale files are the norm, not the exception.)Suggested fix: quick-probe each candidate (is anything listening on that port?) and skip dead ones, preferring a live endpoint over list order; only fall into the 30s wait/raise when no candidate is live.
Bug 2:
_open_chrome_inspect()hardcodes Google Chrome on macOSFor users whose browser is Dia/Arc/Edge, the consent checkbox lives in their browser, so opening Google Chrome is both ineffective and harmful (see step 4 above: it can mint a fresh live debug endpoint in the wrong browser). Related but distinct from #290 (Windows:
webbrowser.open()can't handlechrome://).Suggested fix: open the inspect page in the browser owning the profile dir that discovery matched (or at minimum the system default browser), instead of hardcoded Chrome.
Bug 3 (minor):
_is_local_chrome_mode()ignoresBU_CDP_URLIt only checks
BU_CDP_WS. A user who explicitly pins an endpoint viaBU_CDP_URLstill gets the chrome://inspect popup flow when the connection fails (e.g., while the per-connection "Allow remote debugging" consent dialog is pending in the target browser and the WS handshake times out).Suggested fix: treat an explicit
BU_CDP_URLlikeBU_CDP_WS— a user pinning an endpoint doesn't want the Chrome UI recovery flow.Workaround we use now
A pre-flight script reads the target browser's
DevToolsActivePortand writesBU_CDP_WS=ws://127.0.0.1:9222<path>into the repo.env. That bypasses discovery entirely and (becauseBU_CDP_WSis set) disables the Chrome recovery flow. Works reliably, including across browser restarts (script refreshes the UUID and restarts the daemon when it changes).Happy to send a PR for any/all of the three fixes if you're open to it.