Skip to content

Add BH_NO_FOCUS so tab operations don't raise the browser window - #587

Open
vibrant wants to merge 1 commit into
browser-use:mainfrom
vibrant:no-focus-tabs
Open

Add BH_NO_FOCUS so tab operations don't raise the browser window#587
vibrant wants to merge 1 commit into
browser-use:mainfrom
vibrant:no-focus-tabs

Conversation

@vibrant

@vibrant vibrant commented Aug 6, 2026

Copy link
Copy Markdown

Problem

Target.activateTarget activates the whole application on macOS, not just the window. switch_tab() calls it unconditionally and new_tab() calls switch_tab(), so every tab the harness opens steals keyboard focus from the terminal driving the run. For long agent runs on a desktop that makes the machine unusable — you get yanked out of whatever you were typing every few seconds.

Change

Opt-in BH_NO_FOCUS=1:

  • new_tab() passes background=True to Target.createTarget
  • switch_tab() skips Target.activateTarget
  • the daemon's bootstrap Target.createTarget is backgrounded too

The harness still attaches to and drives the right tab — activation only controls which tab is visibly selected. switch_tab(tid, activate=True) and an explicit cdp("Target.activateTarget", targetId=tid) remain the way to show a tab on purpose; interaction-skills/tabs.md is updated to say so.

Default behaviour is unchanged when the variable is unset.

Notes

Pairs well with launching Chrome via open -g on macOS, plus --disable-backgrounding-occluded-windows --disable-renderer-backgrounding --disable-background-timer-throttling — without those an occluded window stops producing frames and throttles timers.

Verification

macOS 26.5, Chrome 151, dedicated automation profile:

  • three new_tab() calls plus a switch_tab() with BH_NO_FOCUS=1 — frontmost app never changed; with BH_NO_FOCUS=0 it changed to Chrome as before
  • capture_screenshot() on a never-activated tab renders the page correctly (not blank)
  • setInterval(…, 10) on a never-activated tab: 300 ticks in 3s, i.e. unthrottled
  • same tab sequence A/B'd with the variable set and unset produced identical results
  • pytest tests/unit — 97 passed

Summary by cubic

Add an opt-in BH_NO_FOCUS=1 mode to stop tab operations from raising the Chrome window, so long runs don’t steal focus from the terminal on macOS. Tabs open in the background and activation is skipped; default behavior is unchanged.

  • New Features
    • new_tab() uses Target.createTarget with background=True when BH_NO_FOCUS=1.
    • switch_tab(target, activate=None) skips Target.activateTarget by default when BH_NO_FOCUS=1; use switch_tab(tid, activate=True) or cdp("Target.activateTarget", ...) to show a tab.
    • Daemon bootstrap tab creation passes background under BH_NO_FOCUS.
    • Updated docs in interaction-skills/tabs.md.

Written for commit 9f50d2e. Summary will update on new commits.

Review in cubic

Target.activateTarget activates the whole application on macOS, so every
new_tab()/switch_tab() steals keyboard focus from the terminal driving the
run. With BH_NO_FOCUS=1, tabs are created with Target.createTarget's
background flag and activation is skipped; the harness still attaches to and
drives the tab, it just isn't the visibly-selected one.

Unset (the default) behaviour is unchanged. switch_tab(tid, activate=True)
and an explicit cdp("Target.activateTarget", ...) remain the way to show a
tab on purpose.
@browser-harness-review

Copy link
Copy Markdown

✅ Skill review passed

Reviewed 1 file(s) — no findings.

@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.

1 issue found across 3 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/helpers.py">

<violation number="1" location="src/browser_harness/helpers.py:344">
P2: Default `new_tab()` now sends the optional `background` parameter even when no-focus mode is disabled, which can break headless-shell CDP endpoints where that parameter is unsupported. Preserve the old call when `_no_focus()` is false and add `background=True` only for the opt-in path.</violation>
</file>

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

Fix all with cubic | Re-trigger cubic

except Exception:
pass
tid = cdp("Target.createTarget", url="about:blank")["targetId"]
tid = cdp("Target.createTarget", url="about:blank", background=_no_focus())["targetId"]

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 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: Default new_tab() now sends the optional background parameter even when no-focus mode is disabled, which can break headless-shell CDP endpoints where that parameter is unsupported. Preserve the old call when _no_focus() is false and add background=True only for the opt-in path.

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

<comment>Default `new_tab()` now sends the optional `background` parameter even when no-focus mode is disabled, which can break headless-shell CDP endpoints where that parameter is unsupported. Preserve the old call when `_no_focus()` is false and add `background=True` only for the opt-in path.</comment>

<file context>
@@ -323,7 +341,7 @@ def new_tab(url="about:blank"):
         except Exception:
             pass
-    tid = cdp("Target.createTarget", url="about:blank")["targetId"]
+    tid = cdp("Target.createTarget", url="about:blank", background=_no_focus())["targetId"]
     switch_tab(tid)
     if url != "about:blank":
</file context>
Suggested change
tid = cdp("Target.createTarget", url="about:blank", background=_no_focus())["targetId"]
create_params = {"url": "about:blank"}
if _no_focus():
create_params["background"] = True
tid = cdp("Target.createTarget", **create_params)["targetId"]
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