Skip to content

daemon: give each named daemon its own tab on shared browsers - #616

Open
teknium1 wants to merge 1 commit into
browser-use:mainfrom
teknium1:fix/named-daemon-dedicated-tab
Open

daemon: give each named daemon its own tab on shared browsers#616
teknium1 wants to merge 1 commit into
browser-use:mainfrom
teknium1:fix/named-daemon-dedicated-tab

Conversation

@teknium1

@teknium1 teknium1 commented Aug 15, 2026

Copy link
Copy Markdown

Problem

BU_NAME already isolates a daemon's IPC socket, log, and pid — but on a shared local/CDP browser, every named daemon still runs attach_first_page() against the same Chrome, so parallel daemons all attach to the same tab and clobber each other's navigations. This is the root of the parallel-subagent pain in #375 and #582 (and reported against downstream integrations embedding the harness — e.g. @shantanugoel's report against Hermes Agent's browser_exec).

Change

A named daemon (BU_NAME != "default") on a local/CDP browser now creates its own dedicated about:blank tab via Target.createTarget and attaches to that, instead of grabbing the first existing page. On shutdown it closes the tab it created (owns_target), so workers don't leak tabs into the shared browser.

Unchanged:

  • the default daemon keeps attach-first-page (single-user flow: reuse the tab the user is looking at);
  • named cloud daemons (BU_BROWSER_ID) keep first-page attach — the remote browser is exclusive to the daemon; creating a tab there would just leak one.

This makes BU_NAME the complete parallelism primitive the SKILL.md already implies: name → own socket → own tab. Combined with per-name tab identity work (#604) operators can also tell workers apart.

Validation

  • tests/unit: 109 passed (3 new: named-creates-dedicated-tab, default-keeps-first-page, named-cloud-keeps-first-page).
  • Live E2E against one shared headless Chrome: two named daemons (wa, wb) created distinct targets, set different document.titles, read them back intact across separate invocations; meta:shutdown closed the owned tab (tab count returned to baseline, "closed owned tab" in the daemon log).

Smallest diff that fixes the clobber: one guard clause in attach_first_page() plus best-effort cleanup in main().


Summary by cubic

Named daemons on shared local/CDP browsers now create and attach to their own about:blank tab instead of reusing the first existing page, preventing parallel daemons from clobbering each other’s navigations. The default daemon and named cloud daemons keep first-page attach; named local daemons close their owned tab on shutdown to avoid leaks.

Review notes

  • Add a guard in attach_first_page(): when NAME != "default" and not REMOTE_ID, call Target.createTarget, attach to it, set owns_target, and enable default domains.
  • Add best-effort cleanup in main(): on shutdown, call Target.closeTarget for owned tabs (with timeout and logging).
  • Add unit tests that cover named local creates a dedicated tab, default keeps first-page attach, and named cloud keeps first-page attach.

Written for commit a1aded1. Summary will update on new commits.

Review in cubic

BU_NAME already isolates a daemon's socket, log, and pid, but every
local/CDP daemon still ran attach_first_page() against the same shared
Chrome — so parallel named daemons all attached to the SAME tab and
fought over navigations (browser-use#375, browser-use#582; also reported against downstream
integrations).

Now a named daemon (BU_NAME != "default") on a local/CDP browser creates
its own dedicated about:blank tab via Target.createTarget and attaches
to that, instead of grabbing the first existing page. On shutdown the
daemon closes the tab it created, so parallel workers don't leak tabs
into the shared browser.

Unchanged behavior:
- the default daemon keeps attaching to the first real page (single-user
  flow: reuse the tab the user is looking at);
- named cloud daemons (BU_BROWSER_ID) keep first-page attach — the
  remote browser is exclusive to the daemon, and creating a tab there
  would just leak one.

Verified live against a shared headless Chrome: two named daemons
("wa"/"wb") created distinct targets, set different page titles, read
them back intact across calls, and the owned tab was closed on
meta:shutdown (tab count returned to baseline). tests/unit: 109 passed.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/browser_harness/daemon.py">

<violation number="1" location="src/browser_harness/daemon.py:376">
P2: On a stale-session re-attach, a named daemon creates a brand-new dedicated tab and overwrites `owns_target`/`target_id`, so the previous tab it created in `attach_first_page()` is never closed. Only the last tab is closed at shutdown, so each re-attach leaks one `about:blank` into the shared browser. Track the previous owned target and `Target.closeTarget` it (best-effort) before re-creating a tab when `self.owns_target and self.target_id` is already set.</violation>

<violation number="2" location="src/browser_harness/daemon.py:377">
P2: If target creation succeeds but attach or startup setup fails, the daemon exits before its cleanup block and leaves an orphan tab in the shared browser. Record the created ID immediately and clean it up from the attach/startup failure path.</violation>

<violation number="3" location="src/browser_harness/daemon.py:386">
P2: When startup follows the Chrome remote-debugging recovery flow, this early return skips inspect-tab cleanup and marker removal. Run the same cleanup for the named local path before returning.</violation>

<violation number="4" location="src/browser_harness/daemon.py:642">
P1: After a named daemon switches tabs, `self.target_id` no longer identifies the tab it created. Shutdown therefore closes the selected tab and leaks the owned tab; track a separate `owned_target_id` and close that ID.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

# A named daemon owns the tab it created — close it on shutdown so
# parallel workers don't leak about:blank/leftover tabs into the
# shared browser. Best-effort: the WS may already be gone.
if d.owns_target and d.target_id:

@cubic-dev-ai cubic-dev-ai Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: After a named daemon switches tabs, self.target_id no longer identifies the tab it created. Shutdown therefore closes the selected tab and leaks the owned tab; track a separate owned_target_id and close that ID.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/browser_harness/daemon.py, line 642:

<comment>After a named daemon switches tabs, `self.target_id` no longer identifies the tab it created. Shutdown therefore closes the selected tab and leaks the owned tab; track a separate `owned_target_id` and close that ID.</comment>

<file context>
@@ -615,7 +633,21 @@ async def handler(reader, writer):
+        # A named daemon owns the tab it created — close it on shutdown so
+        # parallel workers don't leak about:blank/leftover tabs into the
+        # shared browser. Best-effort: the WS may already be gone.
+        if d.owns_target and d.target_id:
+            try:
+                await asyncio.wait_for(
</file context>
Fix with cubic

# already exclusive to this daemon, so first-page attach stays.
if NAME != "default" and not REMOTE_ID:
tid = (await self.cdp.send_raw("Target.createTarget", {"url": "about:blank"}))["targetId"]
self.owns_target = True

@cubic-dev-ai cubic-dev-ai Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: If target creation succeeds but attach or startup setup fails, the daemon exits before its cleanup block and leaves an orphan tab in the shared browser. Record the created ID immediately and clean it up from the attach/startup failure path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/browser_harness/daemon.py, line 377:

<comment>If target creation succeeds but attach or startup setup fails, the daemon exits before its cleanup block and leaves an orphan tab in the shared browser. Record the created ID immediately and clean it up from the attach/startup failure path.</comment>

<file context>
@@ -360,12 +360,30 @@ def __init__(self):
+        # already exclusive to this daemon, so first-page attach stays.
+        if NAME != "default" and not REMOTE_ID:
+            tid = (await self.cdp.send_raw("Target.createTarget", {"url": "about:blank"}))["targetId"]
+            self.owns_target = True
+            log(f"named daemon {NAME}: created dedicated tab ({tid})")
+            page = {"targetId": tid, "url": "about:blank", "type": "page"}
</file context>
Fix with cubic

self.target_id = tid
log(f"attached {tid} (about:blank) session={self.session}")
await self._enable_default_domains(self.session)
return page

@cubic-dev-ai cubic-dev-ai Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When startup follows the Chrome remote-debugging recovery flow, this early return skips inspect-tab cleanup and marker removal. Run the same cleanup for the named local path before returning.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/browser_harness/daemon.py, line 386:

<comment>When startup follows the Chrome remote-debugging recovery flow, this early return skips inspect-tab cleanup and marker removal. Run the same cleanup for the named local path before returning.</comment>

<file context>
@@ -360,12 +360,30 @@ def __init__(self):
+            self.target_id = tid
+            log(f"attached {tid} (about:blank) session={self.session}")
+            await self._enable_default_domains(self.session)
+            return page
         targets = (await self.cdp.send_raw("Target.getTargets"))["targetInfos"]
         pages = [t for t in targets if is_real_page(t)]
</file context>
Fix with cubic

# daemon its own dedicated tab instead. REMOTE_ID (cloud) browsers are
# already exclusive to this daemon, so first-page attach stays.
if NAME != "default" and not REMOTE_ID:
tid = (await self.cdp.send_raw("Target.createTarget", {"url": "about:blank"}))["targetId"]

@cubic-dev-ai cubic-dev-ai Bot Aug 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: On a stale-session re-attach, a named daemon creates a brand-new dedicated tab and overwrites owns_target/target_id, so the previous tab it created in attach_first_page() is never closed. Only the last tab is closed at shutdown, so each re-attach leaks one about:blank into the shared browser. Track the previous owned target and Target.closeTarget it (best-effort) before re-creating a tab when self.owns_target and self.target_id is already set.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/browser_harness/daemon.py, line 376:

<comment>On a stale-session re-attach, a named daemon creates a brand-new dedicated tab and overwrites `owns_target`/`target_id`, so the previous tab it created in `attach_first_page()` is never closed. Only the last tab is closed at shutdown, so each re-attach leaks one `about:blank` into the shared browser. Track the previous owned target and `Target.closeTarget` it (best-effort) before re-creating a tab when `self.owns_target and self.target_id` is already set.</comment>

<file context>
@@ -360,12 +360,30 @@ def __init__(self):
+        # daemon its own dedicated tab instead. REMOTE_ID (cloud) browsers are
+        # already exclusive to this daemon, so first-page attach stays.
+        if NAME != "default" and not REMOTE_ID:
+            tid = (await self.cdp.send_raw("Target.createTarget", {"url": "about:blank"}))["targetId"]
+            self.owns_target = True
+            log(f"named daemon {NAME}: created dedicated tab ({tid})")
</file context>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant