Skip to content

feat(tabs): cap the number of open tabs - #520

Closed
daniel771277 wants to merge 1 commit into
browser-use:mainfrom
daniel771277:feat/cap-open-tabs
Closed

feat(tabs): cap the number of open tabs#520
daniel771277 wants to merge 1 commit into
browser-use:mainfrom
daniel771277:feat/cap-open-tabs

Conversation

@daniel771277

@daniel771277 daniel771277 commented Jul 14, 2026

Copy link
Copy Markdown

Problem

Nothing ever closes a tab the agent opened. new_tab() only creates them and close_tab() is opt-in, so a long-running setup just accumulates tabs — I found 50+ open in my automation Chrome, and the browser bogs down well before that.

Fix

new_tab() reaps the oldest tabs beyond BH_MAX_TABS (default 10, 0 disables).

  • Ordering comes from the DevTools /json/list endpoint, which is newest-first. Target.getTargets order is not creation order, so it can't be used for this.
  • about:blank tabs are reapable — they're throwaway harness tabs, and excluding them (they match INTERNAL) would let blank tabs pile up forever.
  • The browser's own UI pages (chrome://, devtools://, chrome-extension://) are never touched, and the tab the caller just got back is always kept.

Cost

Negligible. /json/list is ~0.6 ms, and in steady state exactly one Target.closeTarget is added per new_tab().

median new_tab()
no reaping (baseline) ~29 ms
reaping, nothing to close ~19 ms
reaping, one tab closed (steady state) ~32 ms

The differences sit inside the run-to-run noise. Clearing a 30-tab backlog is a one-off ~100 ms.

Verification (macOS 15, Chrome 150)

With 32 tabs open, the next new_tab() settles the browser at 10 tabs and holds there across repeated calls; the tab the caller is working in survives.

Independent of #519 (both touch new_tab(), so whichever lands second needs a trivial rebase).

🤖 Generated with Claude Code


Summary by cubic

Cap the number of open tabs by auto-closing the oldest tabs in new_tab(). Default limit is 10 (configurable via BH_MAX_TABS), reducing tab bloat and keeping Chrome responsive.

  • New Features
    • new_tab() reaps the oldest tabs beyond BH_MAX_TABS (0 disables) using DevTools /json/list (newest-first); the HTTP base is resolved via daemon meta: "http_endpoint" so it works on any discovered port; skipped on cloud/remote (no endpoint).
    • Keeps the newly opened tab; never closes chrome://, devtools://, or chrome-extension:// pages; about:blank tabs are reapable; negligible overhead (at most one extra Target.closeTarget per new_tab()).

Written for commit 5d2e87a. Summary will update on new commits.

Review in cubic

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

All reported issues were addressed across 2 files

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

Fix all with cubic | Re-trigger cubic

Comment thread src/browser_harness/helpers.py Outdated
Nothing ever closes a tab the agent opened — new_tab() only creates them, and
close_tab() is opt-in — so a long-running setup accumulates dozens of tabs
(50+ here) and the browser bogs down.

new_tab() now reaps the oldest tabs beyond BH_MAX_TABS (default 10, 0
disables). Ordering comes from the DevTools /json/list endpoint, which is
newest-first; Target.getTargets order is not creation order. about:blank tabs
are reapable (they are throwaway harness tabs) but the browser's own UI pages
are never touched, and the tab the caller just got back is always kept.

The /json/list endpoint is resolved from the DevTools HTTP base the daemon
actually connected to (new "http_endpoint" IPC meta) rather than a guessed
port, so reaping works for a browser discovered on any port, not just 9222.
It returns None for cloud/remote browsers, where reaping is skipped.

Cost is negligible: /json/list is ~0.6 ms, and in steady state exactly one
Target.closeTarget is added per new_tab (~32 ms per new_tab, within the noise
of the unreaped baseline). Clearing a 30-tab backlog is a one-off ~100 ms.

Verified on macOS 15 / Chrome 150: with 24 tabs open, the next new_tab()
settles the browser at the cap and holds there, the current tab survives, and
the endpoint derivation is correct for ws/wss/discovered-port/cloud inputs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@daniel771277

Copy link
Copy Markdown
Author

Good catch — fixed in the latest push. _reap_tabs no longer assumes port 9222 when BU_CDP_URL is unset. The daemon now exposes an http_endpoint IPC meta that returns the DevTools HTTP base it actually connected to (derived from the resolved ws URL), so reaping works for a browser discovered on any port. It returns None for cloud/remote browsers (REMOTE_ID set), where reaping is skipped.

@daniel771277

Copy link
Copy Markdown
Author

Closing this — handling the change downstream instead. Thanks!

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