Cold-start auto-launch fails: daemon exits before the chrome-not-running rescue branch can fire
Summary
When Chrome has zero running processes (a true cold start — not just "CDP port not up yet"), browser-harness fails outright with daemon default didn't come up, instead of auto-launching Chrome as advertised since v0.1.8 ("If no Chromium-family browser is running, the harness launches one for you").
Confirmed regression between 0.1.9 and 0.1.13 (0.1.9 auto-launched correctly in this exact scenario, verified twice against real automated worker processes before upgrading; 0.1.13 fails immediately, reproduced 3 times in a row with a clean environment — Chrome fully quit, no stale singleton lock files, fresh daemon log each time).
Environment
- macOS (Darwin 25.6.0)
- browser-harness 0.1.13 (pypi install)
- Python 3.11.15
Reproduction
osascript -e 'quit app "Google Chrome"'
# confirm zero Chrome processes: ps aux | grep "Google Chrome.app/Contents/MacOS/Google Chrome "
rm -f ~/.config/browser-harness/tmp/bu-default.log
browser-harness <<'PY'
new_tab("https://example.com")
print(page_info())
PYActual: immediate failure —
browser-harness: daemon default didn't come up -- check ~/.config/browser-harness/tmp/bu-default.logwith bu-default.log containing exactly one line:
fatal: chrome-not-running: no supported Chromium-family browser is running -- start Chrome, then retryNo "Chrome isn't running — launching it" message is ever printed, and no Chrome process is spawned.
Expected: the harness launches Chrome automatically (per the v0.1.8 auto-launch feature) and proceeds normally.
Root cause (traced in the installed package source)
In browser_harness/daemon.py, the daemon subprocess's own connect-wait loop:
if now >= next_liveness_check:
if not supported_browser_running():
raise RuntimeError(
"chrome-not-running: no supported Chromium-family browser is running -- start Chrome, then retry"
)
next_liveness_check = now + 2raises and lets the daemon subprocess exit almost immediately when Chrome isn't running at all (no 30s grace — the first liveness check trips it).
Back in browser_harness/admin.py, the higher-level orchestrator that's supposed to catch this and auto-launch Chrome only does so on this branch:
if local and launched_browser is None and _chrome_not_running(msg):
# Chrome is closed — launch the browser and retry
restart_daemon(name)
launched_browser = _launch_browser()
...But that branch is only reached if the daemon subprocess is still alive while its log reports chrome-not-running (i.e. pending_died is False). Since the daemon subprocess actually exits immediately in the true-cold-start case, the orchestrator instead takes the earlier pending_died branch:
if local and pending_died:
...
if permission_wait:
raise RuntimeError("permission-blocked: ...")
continue # <-- just respawns the SAME daemon again, up to 3x, never calling _launch_browser()permission_wait is False for this message (it only checks for handshake-wait / permission-popup prefixes), so it falls through to continue, which just retries spawning the daemon from scratch — without ever calling _launch_browser(). After 3 identical failed attempts it gives up with daemon default didn't come up.
Auto-launch still works fine for the other chrome-not-running-ish case (Chrome running but CDP port not up yet, e.g. right after a fresh launch by something else) — that path polls _log_tail while the daemon subprocess is still looping, so the daemon-alive branch is reached correctly. It's specifically the "daemon dies immediately because zero Chrome processes exist" case that breaks the auto-launch rescue.
Suggested fix
Either:
- Have the
daemon.pyconnect-wait loop not immediately exit on the very first failed liveness check when Chrome is fully absent — give the orchestrator's own launch-and-retry a chance to run first, or - Have
admin.py'spending_diedbranch also check_chrome_not_running(msg)and call_launch_browser()before falling through to a barecontinue, matching what the still-alive branch already does.
Also observed (may be unrelated, noting for completeness)
browser-harness --doctor's "chrome running" check (_chrome_running() in admin.py) does a naive case-insensitive substring match against ps -A -o comm= output for names like "chrome". This produces false positives against unrelated processes that merely contain "chrome" in their path, e.g. Electron apps' own chrome_crashpad_handler helper (observed here from an unrelated app, not Google Chrome itself). Doctor reported chrome running: ok while the real Google Chrome.app process count was zero. Worth a more precise check (e.g. matching on the actual browser binary path/bundle id) if this hasn't already been fixed elsewhere.
Source: browser-use/browser-harness