diff --git a/src/browser_harness/daemon.py b/src/browser_harness/daemon.py index de51934..fb443c2 100644 --- a/src/browser_harness/daemon.py +++ b/src/browser_harness/daemon.py @@ -207,9 +207,22 @@ class Daemon: targets = (await self.cdp.send_raw("Target.getTargets"))["targetInfos"] pages = [t for t in targets if is_real_page(t)] if not pages: - # No real pages — create one instead of attaching to omnibox popup - tid = (await self.cdp.send_raw("Target.createTarget", {"url": "about:blank"}))["targetId"] - log(f"no real pages found, created about:blank ({tid})") + # No real pages: attach to an existing plain page tab (e.g. an + # about:blank left by a prior attach) before creating anything. + # is_real_page() rejects about: urls, so without this fallback a + # blank-only browser mints a NEW blank on every re-attach, forever. + pages = [t for t in targets if t.get("type") == "page"] + if pages: + log(f"no real pages found, reusing {pages[0].get('url','')[:60]} ({pages[0]['targetId']})") + if not pages: + # Truly no page tabs — create one instead of attaching to omnibox + # popup. background=True: a foreground Target.createTarget raises + # the Chrome window and steals macOS app focus (2026-07-15 7/7 + # same-second correlation); a re-attach must never pop a window. + tid = (await self.cdp.send_raw( + "Target.createTarget", {"url": "about:blank", "background": True} + ))["targetId"] + log(f"no page tabs at all, created background about:blank ({tid})") pages = [{"targetId": tid, "url": "about:blank", "type": "page"}] self.session = (await self.cdp.send_raw( "Target.attachToTarget", {"targetId": pages[0]["targetId"], "flatten": True}