9182 Commits

Author SHA1 Message Date
laithrw
184c49713e fix(#4631): clear dom cache after scroll to prevent stale extract data (#4658)
Resolves #4631

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Clears the DOM watchdog cache after scrolls to prevent stale extraction
data and ensure DOM reads reflect the current page state. Addresses
#4631 where post-scroll data could be outdated.

- **Bug Fixes**
- Clear `_dom_watchdog` cache after both element-target and CDP gesture
scrolls.
- Wait ~200ms before clearing after element-target scroll so the DOM can
settle.

<sup>Written for commit 60e7767228.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-11 18:09:26 -04:00
laithrw
60e7767228 Merge branch 'main' into issue-4631-review 2026-04-11 18:07:12 -04:00
Laith Weinberger
534eaafe7a clear dom cache after scroll to prevent stale extract data 2026-04-11 18:03:06 -04:00
grtninja
81089417fa ci: pin stale workflow action 2026-04-11 03:13:48 -04:00
Alexander Yue
cd60e6bdd5 Add per-link utm_medium slugs to README cloud links (#4653)
## Summary
- Differentiates the 8 cloud-bound README links by changing
`utm_medium=readme` to `utm_medium=readme-{slug}` (e.g.
`readme-badge-cloud`, `readme-skip-setup`, `readme-faq-captcha`)
- Enables per-placement attribution in PostHog without adding extra UTM
fields
- `utm_source=github` is unchanged so existing filters still work

## Slugs

| Link | `utm_medium` |
|---|---|
| Download stats badge | `readme-badge-downloads` |
| Cloud badge (nav) | `readme-badge-cloud` |
| "Skip the setup" CTA | `readme-skip-setup` |
| Quickstart API key | `readme-quickstart-api-key` |
| Cloud docs | `readme-cloud-docs` |
| Fully-Hosted Cloud Agent | `readme-hosted-agent` |
| CAPTCHA FAQ | `readme-faq-captcha` |
| Production FAQ | `readme-faq-production` |
2026-04-10 15:53:11 -07:00
Alezander9
889ccb81bb Add per-link utm_medium slugs to README cloud links for placement attribution 2026-04-10 15:48:51 -07:00
Alexander Yue
eeb767ef18 Improve OSS-to-cloud conversion: UTM tracking, better error messages, and cloud nudges (#4646)
## Summary

- Add UTM tracking params to all cloud-bound links across README, CLI,
and error messages — enables measuring which OSS surfaces drive cloud
sign-ups
- Rewrite README "Open Source vs Cloud" section: position cloud browsers
as the recommended pairing for OSS users, remove separate "Use Both"
section
- Rewrite error messages for `use_cloud=True` and `ChatBrowserUse()` to
clearly state what is wrong and what to do next (not vague
"authentication failed" messages)
- Add missing URLs to dead-end errors: invalid API key now links to key
page, insufficient credits now links to billing page
- Add cloud browser nudge on captcha detection (`logger.warning`)
- Add cloud browser nudge on local browser launch failure (Chromium not
installed, etc.)
- Fix pre-existing pyright error with `readline.add_history` on Windows

## Changes by file

**README.md** — UTM params on 8 cloud links, "Use Both" section folded
into "Use Open Source" with cloud browsers as recommended pairing

**browser_use/agent/service.py** — Captcha nudges rewritten as
`logger.warning` with cloud URL + UTM

**browser_use/browser/cloud/cloud.py** — Error messages rewritten:
"BROWSER_USE_API_KEY is not set" / "is invalid" + UTM on URLs

**browser_use/browser/session.py** — Auth error re-raise simplified (no
rewrap), new nudge on local browser launch failure

**browser_use/llm/browser_use/chat.py** — Error messages rewritten,
added URL to 401 (invalid key) and billing URL to 402 (insufficient
credits)

**browser_use/cli.py** — UTM params added to all cloud URLs, fix
`readline.add_history` pyright error on Windows by using `getattr` at
import time

**browser_use/init_cmd.py** — UTM param added to API key URL

**browser_use/skill_cli/commands/cloud.py** — UTM param added to API key
URL

## UTM scheme

| utm_source | utm_medium | Where |
|---|---|---|
| `github` | `readme` | README links |
| `oss` | `cli` | CLI init/TUI messages |
| `oss` | `use_cloud` | `Browser(use_cloud=True)` errors |
| `oss` | `chat_browser_use` | `ChatBrowserUse()` errors |
| `oss` | `captcha_nudge` | Runtime captcha detection |
| `oss` | `browser_launch_failure` | Local browser launch failure |
2026-04-08 22:19:38 -07:00
Alezander9
f8f7be2e90 Fix test assertions to match updated error messages 2026-04-08 22:12:16 -07:00
Alezander9
76569995fd Improve OSS-to-cloud conversion: UTM tracking, better error messages, and cloud nudges
- Add UTM params to all cloud-bound links across README, CLI, and error messages
- Rewrite README Open Source vs Cloud section: position cloud browsers as
  recommended pairing for OSS users, remove separate Use Both section
- Rewrite error messages for use_cloud=True and ChatBrowserUse() to clearly
  state what is wrong and what to do next
- Add missing URLs: invalid API key now links to key page, insufficient
  credits now links to billing page
- Add cloud browser nudge on captcha detection (logger.warning)
- Add cloud browser nudge on local browser launch failure
2026-04-08 22:05:50 -07:00
Saurav Panda
fa17b89846 chore: update browser-use-sdk from 2.0.15 to 3.4.2 (#4644)
## Summary
- Updates `browser-use-sdk` dependency from `2.0.15` to `3.4.2`
- All existing imports verified working with the new version

## Test plan
- [x] `uv sync` succeeds
- [x] All SDK imports (`AsyncBrowserUse`, type imports) verified working
- [ ] CI tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Upgrade `browser-use-sdk` from 2.0.15 to 3.4.2 and align our code with
v3 API changes. No behavior changes.

- **Dependencies**
  - Updated `browser-use-sdk` to `3.4.2` in `pyproject.toml`.

- **Refactors**
- Moved to new top‑level SDK imports (`AsyncBrowserUse`,
`ExecuteSkillResponse`, `SkillListResponse`, `ParameterSchema`,
`SkillResponse`).
- Handle UUID skill IDs from the SDK by casting to and comparing as
`str`.
- On error, return `ExecuteSkillResponse` with `result=None`,
`stderr=None`, and `latencyMs=None` for schema compatibility.

<sup>Written for commit 1a94f96ce9.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-08 18:28:17 -07:00
Saurav Panda
1a94f96ce9 fix: update imports for browser-use-sdk 3.4.2 and handle UUID id fields 2026-04-08 17:51:22 -07:00
Saurav Panda
c690af2051 chore: update browser-use-sdk from 2.0.15 to 3.4.2 2026-04-08 17:41:57 -07:00
laithrw
92d3d152a2 fix: guard against missing stdin in MCP stdio server startup (#4642)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Guard `mcp.server.stdio` startup against missing stdin. When stdin is
absent, raise a clear RuntimeError so the process fails fast instead of
producing ambiguous startup errors.

<sup>Written for commit 83317179cb.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-08 17:29:24 -04:00
laithrw
83317179cb Merge branch 'main' into fix/mcp-stdin-guard 2026-04-08 17:25:16 -04:00
laithrw
fa7bae3400 fix: use setattr for LLM ainvoke patching to avoid pydantic crash (#4641)
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes runtime crashes when patching LLM ainvoke on Pydantic-backed
models by using `object.__setattr__` to assign `tracked_ainvoke`. This
bypasses Pydantic attribute validation and keeps the tracking wrapper
working.

<sup>Written for commit 3242d9dbc2.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-08 17:24:47 -04:00
Laith Weinberger
674547a414 fix: guard against missing stdin in MCP stdio server startup 2026-04-08 17:21:11 -04:00
Laith Weinberger
3242d9dbc2 fix: use object.__setattr__ for LLM ainvoke patching to avoid pydantic crash 2026-04-08 17:20:46 -04:00
laithrw
8de903dfdd fix: prevent KeyError in load_from_dict and IndexError in final_result (#4464)
## Fixes #4463

### Problems

**1. `AgentHistoryList.load_from_dict` — KeyError on missing keys**

The current implementation uses direct dict subscript access that
crashes when loading history saved with an older browser-use version or
when a step entry is missing expected keys:

```python
for h in data['history']:           # KeyError if 'history' key absent
    if h['model_output']:           # KeyError if 'model_output' absent
    if 'interacted_element' not in h['state']:  # KeyError if 'state' absent
```

**2. `AgentHistoryList.final_result()` — IndexError on empty result
list**

```python
if self.history and self.history[-1].result[-1].extracted_content:
#                              ^^^^^^^^^^^^^^^^ IndexError when result == []
```

Every sibling method (`is_done`, `is_successful`, `judgement`,
`is_judged`, `is_validated`) already guards with
`len(self.history[-1].result) > 0`. `final_result` was the only one
missing this check.

### Fix

- **`load_from_dict`**: use `data.get('history', [])` and `h.get(key)`
instead of direct subscript access, matching standard defensive Python
dict usage.
- **`final_result`**: add `len(self.history[-1].result) > 0` guard
before accessing `result[-1]`, consistent with all other methods in the
class.

### Changes

Only `browser_use/agent/views.py` is modified (14 additions, 8
deletions).

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Prevent crashes when loading agent history with missing fields and when
reading the final result on empty lists. This makes `AgentHistoryList`
safer and backward-compatible with legacy or partial histories.

- **Bug Fixes**
- `load_from_dict`: use `data.get('history', [])` and `h.get()` for safe
access; initialize missing `state` and default
`state.interacted_element` to `None`; validate `model_output` only when
it’s a dict.
- `final_result`: check `len(self.history[-1].result) > 0` before
indexing; return `None` when empty or no extracted content, consistent
with other methods.

<sup>Written for commit 6129ceb0b0.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-07 14:20:28 -04:00
laithrw
6129ceb0b0 Merge branch 'main' into fix/agent-history-safe-access 2026-04-07 14:18:38 -04:00
laithrw
55ca9cb27d improve model docs (#4616)
In response to #4214

<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Revamped the LLM model docs with a quick-reference table, use‑case
recommendations, and clearer provider setup. Adds native coverage for
more providers, updates examples, and improves env/installation
guidance.

- **Refactors**
- Added Quick Reference (provider → class → env) and use‑case picks
based on benchmarks.
- Renamed to “Browser Use Cloud,” updated pricing/models, and listed
`browser-use/bu-30b-a3b-preview`.
- Expanded provider docs: DeepSeek, Mistral, Cerebras, OpenRouter,
LiteLLM; improved OpenAI/Anthropic/Gemini/Azure/Bedrock/Vercel/OCI
guidance with links.
- Standardized examples to `Agent` + `Chat*` classes and noted key env
vars.
- Moved OpenRouter out of OpenAI-compatible section; trimmed/reorganized
OpenAI-compatible examples.
- Minor fixes: `GOOGLE_API_KEY` deprecation note, Anthropic coordinate
clicking note, “LangChain” capitalization.

<sup>Written for commit c1151715d3.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-05 22:01:37 -04:00
Laith Weinberger
c1151715d3 improve model docs 2026-04-05 21:59:33 -04:00
Saurav Panda
de14b9aa31 fix: add per-session auth token to daemon socket (#4598)
## Summary

- **Root cause**: The daemon accepted any connection on its socket
without authentication. On Windows the port is deterministic
(`adler32(session)`-derived), so any local process could connect and
dispatch the `python` action, executing arbitrary code via
`eval()`/`exec()` as the daemon owner. On Unix the socket may be
world-writable under a permissive umask.
- **Fix**: On `Daemon.run()`, generate a `secrets.token_hex(32)` token,
write it atomically to `~/.browser-use/{session}.token` with `chmod
0o600`. Validate every incoming request with `hmac.compare_digest`
before dispatching. Delete the token file on shutdown.
- **Client**: `send_command()` in `main.py` now reads the token file and
attaches it to every request. Falls back to `''` for old daemons
(no-op).

## Test plan

- [ ] Start daemon, run `browser-use python "1+1"` — should work
normally
- [ ] Send a raw request without token to socket — should get
`{"success": false, "error": "Unauthorized"}`
- [ ] Unauthenticated `shutdown` action should be ignored (daemon stays
up)
- [ ] After daemon stops, `~/.browser-use/default.token` should be
deleted
- [ ] All CI tests pass (`uv run pytest -vxs tests/ci`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Secure the daemon socket with a per-session auth token to block
unauthorized local connections and arbitrary code execution via the
`python` action. The CLI now reads the token and includes it with each
command.

- **Bug Fixes**
- Generate a session token in `Daemon.run()` (`secrets.token_hex(32)`);
write it atomically to `~/.browser-use/{session}.token` via a `*.tmp`
file created with `0o600`, then replace; raise if write fails; delete on
shutdown.
- Validate every request using `hmac.compare_digest`; unauthorized calls
return `{"success": false, "error": "Unauthorized"}` and `shutdown` is
honored only for authorized, successful requests.
- Client `send_command()` reads the token and sends it as `token`; falls
back to `''` for older daemons.

<sup>Written for commit ca2185ba61.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-03 10:54:19 -07:00
sauravpanda
ca2185ba61 fix: create token temp file with 0o600 at open() time; raise on failure
- Use os.open() with mode 0o600 instead of write-then-chmod to eliminate
  the permission race window where the temp file is briefly world-readable.
- Raise instead of warn when token file write fails: a daemon that cannot
  persist its auth token is permanently unauthorized for all clients, so
  failing fast is correct (identified by cubic).
2026-04-02 17:58:12 -07:00
sauravpanda
a05a053da6 fix: add per-session auth token to daemon socket to prevent unauthorized code execution
Generate a secrets.token_hex(32) on daemon startup, write it atomically
to ~/.browser-use/{session}.token (chmod 0o600), and validate it on every
incoming request via hmac.compare_digest. The client reads the token file
and includes it in each send_command() call.

This closes the arbitrary-code-execution vector where any local process
could connect to the deterministic Windows TCP port (or a world-readable
Unix socket) and dispatch the 'python' action to run eval()/exec() as the
daemon owner.
2026-04-02 17:41:15 -07:00
Saurav Panda
9b43d66c53 fix: upgrade requests to 2.33.0 to patch temp-file path-traversal vulnerability (#4597)
## Summary

- Bumps `requests` from `2.32.5` to `2.33.0` in `pyproject.toml`
- Fixes a local privilege escalation / file substitution vulnerability
in `requests.utils.extract_zipped_paths()`: prior versions extracted to
a predictable temp filename with no validation, allowing a local
attacker with write access to the temp directory to pre-create a
malicious file that the library would load instead
- 2.33.0 now extracts to a non-deterministic location, eliminating the
race condition

> **Note:** Standard usage of the Requests library is not affected. Only
applications calling `extract_zipped_paths()` directly are impacted.

## Test plan

- [ ] No functional code changes — version pin bump only
- [ ] `uv sync` resolves cleanly with the new pin

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Upgrade `requests` from `2.32.5` to `2.33.0` to fix a temp-file path
traversal/race in `requests.utils.extract_zipped_paths()` that could
allow local file substitution. Standard `requests` usage isn’t affected;
only direct callers of that helper were exposed, and no app changes are
needed.

<sup>Written for commit a7fb3884c4.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-02 16:45:14 -07:00
Saurav Panda
bd26395428 Merge branch 'main' into worktree-fix+requests-security-upgrade 2026-04-02 16:34:49 -07:00
Saurav Panda
8e98dd46b5 fix: upgrade aiohttp to 3.13.4 to patch memory exhaustion vulnerability (#4596)
## Summary

- Bumps `aiohttp` from `3.13.3` to `3.13.4` in
`browser_use/skill_cli/requirements-cli.txt`
- Fixes a security vulnerability where insufficient restrictions on
trailer header handling could allow uncapped memory usage, potentially
causing memory exhaustion from attacker-controlled requests/responses

Upstream patch: aio-libs/aiohttp@0c2e9da

Closes #26

## Test plan

- [ ] No functional code changes — version pin bump only
- [ ] Verify `install_lite.sh` installs the patched version

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Upgrade `aiohttp` to 3.13.4 in the CLI to patch a memory exhaustion
vulnerability in trailer header handling. Ensures the lite installer
pulls the fixed version.

- **Dependencies**
- Bump `aiohttp` from `3.13.3` to `3.13.4` in
`browser_use/skill_cli/requirements-cli.txt` to prevent uncapped trailer
header processing that could lead to memory exhaustion.

<sup>Written for commit 14ada65183.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-02 16:34:40 -07:00
Saurav Panda
14ada65183 Merge branch 'main' into worktree-fix+aiohttp-security-upgrade 2026-04-02 16:31:45 -07:00
Saurav Panda
a7fb3884c4 Merge branch 'main' into worktree-fix+requests-security-upgrade 2026-04-02 16:31:38 -07:00
sauravpanda
59ef9adeb6 fix: upgrade requests to 2.33.0 to patch temp-file path-traversal vulnerability
Bumps requests from 2.32.5 to 2.33.0.
extract_zipped_paths() previously wrote to a predictable temp path with no
validation, allowing a local attacker to pre-create a malicious file that
would be loaded in its place. 2.33.0 extracts to a non-deterministic
location, eliminating the race condition.
2026-04-02 16:28:49 -07:00
Saurav Panda
e2ce72fd94 fix: security and correctness issues found in #4514 review (#4590)
## Summary

Three security and correctness issues found during review of #4514,
which has since been merged. All three affect the new `skill_cli` layer
introduced by that PR.

- **`BROWSER_USE_API_KEY` env var silently ignored**
(`commands/cloud.py`): #4514 dropped the env var fallback in
`_get_api_key()` without a migration path, breaking CI/CD pipelines that
set it as a secret. Restored with a deprecation warning directing users
to `browser-use config set api_key`.

- **`_install_cloudflared()` downloads binary without integrity check**
(`commands/setup.py`, Linux only): Raw `urllib.request.urlretrieve`
wrote directly to the install destination with no verification. Now
downloads to a temp file, fetches the `.sha256sum` Cloudflare publishes
alongside each release, verifies SHA256 before installing, and cleans up
on failure. macOS (`brew`) and Windows (`winget`) were already safe —
they verify internally.

- **`write_config()` not atomic** (`config.py`): Direct
`path.write_text()` truncates `config.json` on `SIGKILL` mid-write;
`read_config()` catches `json.JSONDecodeError` and returns `{}`,
silently wiping the API key and all settings. Now uses
`tempfile.mkstemp(dir=same_dir)` + `fsync` + `os.replace()` — the same
pattern `_write_state()` in `daemon.py` already uses correctly.

## Test plan

- [ ] `BROWSER_USE_API_KEY=sk-xxx browser-use cloud connect` prints
deprecation warning and still authenticates
- [ ] After `browser-use config set api_key sk-xxx`, commands work
without the env var set
- [ ] On Linux: cloudflared install rejects a tampered binary with a
clear SHA256 mismatch error
- [ ] `SIGKILL` during `browser-use config set` leaves `config.json`
intact or absent, never truncated

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-02 16:27:36 -07:00
sauravpanda
22f0e501e9 fix: upgrade aiohttp to 3.13.4 to patch memory exhaustion vulnerability
Bumps aiohttp from 3.13.3 to 3.13.4 in requirements-cli.txt.
Fixes uncapped memory usage from insufficient trailer header restrictions
(aio-libs/aiohttp@0c2e9da).
2026-04-02 16:26:39 -07:00
sauravpanda
56d8aa8483 fix: address review violations — drop env var fallback, fix cross-fs move
- cloud.py: remove BROWSER_USE_API_KEY env var fallback (violates CLI
  policy of config.json as single source of truth); instead detect the
  env var in the error path and print a targeted migration hint
- setup.py: replace Path.rename() with shutil.move() so the temp file
  can be moved across filesystems (e.g. /tmp -> /usr/local/bin)
2026-04-02 16:21:24 -07:00
sauravpanda
ea99055e53 fix: write config.json atomically via tmp+rename to prevent silent data loss
A SIGKILL mid-write truncates config.json; read_config() catches
json.JSONDecodeError and returns {}, silently wiping the API key and
all other settings. Mirror the pattern already used by _write_state():
write to a sibling temp file, fsync, chmod 600, then os.replace() into
place — which is atomic on POSIX and effectively atomic on Windows.
2026-04-02 13:08:37 -07:00
sauravpanda
7a887e156e fix: verify cloudflared binary SHA256 checksum before installing on Linux
Downloads to a temp file, fetches the .sha256sum file Cloudflare publishes
alongside each release, and verifies before moving to the install destination.
Protects against MITM/CDN tampering. Temp file is cleaned up on failure.
2026-04-02 13:08:37 -07:00
sauravpanda
96bb65dcb5 fix: warn on deprecated BROWSER_USE_API_KEY env var instead of silently ignoring it
The CLI previously accepted the env var as a fallback; this PR dropped it
without a migration path, breaking CI/CD pipelines that set it as a secret.
Restore backwards-compat by checking the env var after config.json and
printing a deprecation warning with the migration command.
2026-04-02 13:08:37 -07:00
shawn pana
ad87b45200 another big cli update (#4514)
Multiple agents can share one browser via --connect without interfering
with each other. Each agent registers with `browser-use register` to get
a numeric index, then passes it with `--connect <index>` on every
command.

- Tab locking: mutating commands (click, type, open) lock the tab to the
agent. Other agents get an error if they try to mutate the same tab.
Read-only commands (state, screenshot) work on any tab.
- Agent registry: agents.json tracks registered agents with timestamps.
Expired agents (5min inactive) get cleaned up automatically.
- Session lock: prevents double BrowserSession creation when two agents
connect simultaneously.
- Focus swap: daemon swaps agent_focus_target_id and cached_selector_map
per-agent before each command, so element indices are isolated.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Replaces multi‑session isolation with per‑session browsers via
`--session`, adds a lightweight direct‑action runtime, tab controls, and
a phase‑aware daemon. Deprecates `--connect`, enables zero‑config `cloud
connect` with config‑driven recording and config‑only cloud auth, adds a
`config` command, strengthens lifecycle probes/close behavior, speeds up
cloud connect by skipping per‑call profile validation, and shows
“Connecting…”/“Closing…” during slow operations.

- **New Features**
- Session isolation: each `--session` has its own daemon, socket,
PID/state files, and browser; sessions run in parallel; `sessions` shows
phase and includes CDP URL only in `--json` (including cloud).
- Tabs: `tab list`, `tab new [url]`, `tab switch <index>`, `tab close
[index...]`; clearer errors that suggest `new`; `close` uses logical
focus; JS dialogs auto‑dismiss and surface in `state`; `state` uses the
focused tab’s title and preserves empty titles.
- Lightweight runtime: `CLIBrowserSession` with direct actions via
`ActionHandler` (no event bus/watchdogs); faster, more reliable scroll
(JS `scrollBy` with left/right fix); `Page.enable` wrapped to avoid
root‑target failures.
- Connect & cloud: one‑time `connect` for local Chrome; `cloud connect`
reads `cloud_connect_proxy`, `cloud_connect_timeout`, and
`cloud_connect_recording` from `browser-use config` (CLI default
recording on; library default remains off); unified base via
`BROWSER_USE_CLOUD_BASE_URL` host (CLI appends `/api/{version}`); `cloud
signup` to get/save API key; API key is read only from
`~/.browser-use/config.json` (env var ignored) and forwarded to
`profile-use`; cloud profile ID is read from config without per‑call GET
validation and auto‑heals on invalid/missing IDs (created only when
needed), with daemon‑safe error handling; empty API key is treated as
unset.
- Config, setup & lifecycle: `browser-use config` (set/get/list/unset)
with secure `config.json`; `doctor` shows config state with docs link;
installers create `config.json` and lite requires `curl`; interactive
`setup` (`--yes` for CI); daemon writes a phase‑aware state file,
includes PID in pings, has idle timeout, cleans up orphaned daemons,
supports cross‑platform shutdown with SIGTERM fallback, and avoids
deleting sockets for live daemons with stale PID files.
- Tests & fixes: real subprocess lifecycle tests; cloud stop timeout;
cloud connect faster via skipped profile validation; tests read API keys
from `config.json`; tolerate shutdown race after SIGTERM.

- **Migration**
- Use `--session <NAME>` for isolation. Removed `--agent`, `browser-use
register`, and tab‑ownership locking.
- Use `tab` subcommands; standalone `switch` and `close-tab` are
removed.
- Run `browser-use connect` once for local Chrome; the `--connect` flag
is deprecated and now errors with guidance.
- For cloud, use `cloud connect` with zero flags; customize via
`browser-use config` (`cloud_connect_proxy`, `cloud_connect_timeout`,
`cloud_connect_recording`); set `BROWSER_USE_CLOUD_BASE_URL` to the host
(CLI appends `/api/{version}`); use `cloud signup` to obtain and save an
API key; set the API key via `browser-use config set api_key <KEY>` —
`BROWSER_USE_API_KEY` is ignored by the CLI.

<sup>Written for commit a7b476ee46.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-02 13:06:16 -07:00
shawn pana
a7b476ee46 Merge branch 'main' into multi-session 2026-04-02 13:02:51 -07:00
ShawnPana
454dbfdaba style: flatten assert to pass pre-commit formatter 2026-04-02 12:34:27 -07:00
ShawnPana
7db93b7ac7 fix: accept shutting-down race in test_close_orphaned_daemon
SIGTERM may not kill daemon before the CLI re-probes, producing a
"daemon may still be shutting down" warning instead of "Browser closed".
2026-04-02 12:28:15 -07:00
ShawnPana
f8fdddc66d fix: update cloud tests to use config.json for API key instead of env var 2026-04-02 11:56:54 -07:00
ShawnPana
0530545c1a fix: API key single source of truth (config.json only), daemon-safe profile creation
- Remove BROWSER_USE_API_KEY env var as a read source from CLI code; config.json is the only source of truth
- Split _create_cloud_profile into daemon-safe _inner (raises) and CLI wrapper (sys.exit)
- Daemon auto-heal no longer kills process on profile creation API errors
2026-04-02 11:51:38 -07:00
ShawnPana
47ba16b8ab ux: show Connecting.../Closing... status during slow operations 2026-04-02 11:29:03 -07:00
ShawnPana
1deb430f8f perf: skip profile validation HTTP call on cloud connect
_get_or_create_cloud_profile reads config instantly instead of
validating via GET /profiles/{id} on every connect. If the profile
is invalid, _provision_cloud_browser auto-heals by creating a new
one and retrying. Saves ~500ms-1s on every cloud connect.
2026-04-02 11:21:12 -07:00
MagMueller
329c67f069 chore: bump version to 0.12.6 0.12.6 2026-04-02 00:49:08 -07:00
Magnus Müller
ef536d4ffe perf: fix O(n²) bottlenecks in DOM capture for heavy pages (#4587)
Four small fixes for DOM capture performance. **4 files changed, +51
lines, -7 lines.**

## Fixes

### 1. `enhanced_snapshot.py` — Convert `isClickable` list to set before
loop

`_parse_rare_boolean_data` used `index in list` (O(n) per call), called
per node = O(n²).

```python
# Before: O(n) list scan per node
return index in rare_data['index']  # List[int]

# After: O(1) set lookup
is_clickable_set = set(nodes['isClickable']['index'])  # once
return index in rare_data_set  # per node
```

**20k elements: 14,160ms → 2,973ms. 100k elements: 356s → 9s.**

### 2. `paint_order.py` — Cap `RectUnionPure` at 5,000 rects

Each `add()` can split existing rects into up to 4 pieces. With
overlapping translucent layers this grows exponentially. `_MAX_RECTS =
5000` stops it. When hit, `contains()` returns False — less aggressive
filtering, same correctness.

**20k elements: 372s → 4.7s.**

### 3. `service.py` — Skip JS listener detection on pages with >10k
elements

Early bail-out inside the existing JS expression. Interactive elements
still detected via accessibility tree + `ClickableElementDetector`.

### 4. `dom_watchdog.py` — Add 2s timeout to pending network request
check

`_get_pending_network_requests()` had no timeout. On slow CI machines or
pages mid-navigation, it hangs for 15s+ silently eating into the 30s
`BrowserStateRequestEvent` budget. This causes the DOM capture to
timeout, producing 5 consecutive failures → agent termination.

Found by tracing eval failures on eBay search results where the
DOMWatchdog produced zero log output for 15 seconds between "STARTING"
and the timeout.

## Benchmarks

Profiled every pipeline stage on `main` vs this branch:

| Stage | main (20k) | This PR (20k) |
|---|---:|---:|
| `build_snapshot_lookup` | 14,160ms | **2,973ms** |
| JS listener detection | 2,326ms | **0ms** |
| `calculate_paint_order` | 372,303ms | **4,683ms** |
| Everything else | ~12s | ~8s |
| **Total** | **~400s** | **~16s** |

## Eval results

Ran WebBench_READ_v5 (198 tasks) on both branches, 4 runs total.
Task-by-task comparison on 35 common tasks:

- Always pass (both branches): 6
- Always fail (both branches): 13
- **Main always better: 0**
- **Ours always better: 1**
- Flaky (random): 15

No regressions. Normal pages (<2k nodes) unaffected — same timings, same
behavior.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it changes DOM capture heuristics (timeouts and
early-bail conditions) that can affect which elements are marked
clickable/visible or filtered by paint order on very large pages.
> 
> **Overview**
> Speeds up DOM capture on very large pages by removing a key O(n²)
hotspot and adding protective caps/timeouts to avoid long stalls.
> 
> `enhanced_snapshot.build_snapshot_lookup` now converts CDP
`isClickable.index` to a `set` once and uses O(1) membership checks per
node. Paint-order filtering adds a 5k-rectangle safety cap in
`RectUnionPure.add()` to prevent runaway rect fragmentation, and JS
click-listener detection skips entirely when the page has >10k elements.
> 
> `DOMWatchdog` also wraps the pending-network-request precheck in a 2s
`asyncio.wait_for` and logs a timeout, preventing this step from
consuming most of the `BrowserStateRequestEvent` budget.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3aa68384ad. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2026-04-02 00:47:07 -07:00
ShawnPana
63904858f4 fix: CDP URL only in sessions --json, not in table output 2026-04-01 22:40:48 -07:00
ShawnPana
8c6d042a79 fix: truncate CDP URL in sessions table, full URL in --json 2026-04-01 22:39:30 -07:00
ShawnPana
b1522b5e23 fix: sessions shows CDP URL for cloud sessions too
Ping response now returns live CDP URL from the browser session
(not just the constructor arg). Cloud sessions show their
provisioned CDP URL.
2026-04-01 22:37:23 -07:00
ShawnPana
c09ea5a0b2 feat: sessions command shows CDP URL for each session 2026-04-01 22:35:11 -07:00