9878 Commits

Author SHA1 Message Date
Laith Weinberger 9fb49e03ee sync browser-use skill from harness main (cloud-browser section) 2026-07-07 16:29:37 -07:00
laithrw 50a2da3de9 hint if trying to use browser-use cmd (#5169)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Adds a quickstart and better stdin handling to the `browser-use` CLI
when run without args, guiding new users and preventing empty runs.

- **New Features**
- Show a quickstart message and exit 0 when `browser-use` is invoked
with no args in a TTY.
- Run piped Python from stdin; if stdin is empty, print a hint with
usage and exit 1.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5169?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 16:23:36 -07:00
laithrw 755083ebe5 Merge branch 'main' into fix/bare-invocation-quickstart 2026-07-07 16:17:51 -07:00
Laith Weinberger c93a1de56f hint if trying to use browser-use cmd 2026-07-07 16:17:14 -07:00
laithrw 2e2ebe829c Bump browser-use version to 0.13.4 (#5168)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Bump `browser-use` from 0.13.3 to 0.13.4 to prepare the next patch
release. Updates `pyproject.toml` only; no functional changes.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5168?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 15:49:35 -07:00
laithrw fcd24b2bb2 Merge branch 'main' into bump-vers 2026-07-07 15:48:01 -07:00
Laith Weinberger f1a1d666c1 Bump browser-use version to 0.13.4 2026-07-07 15:46:42 -07:00
laithrw 703e8356cb remove telemetry (#5165)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Removed in-repo telemetry and delegated CLI event reporting to
`browser_harness` for unified, simpler telemetry. No user-facing
behavior changes; events report via Harness when available.

- **Refactors**
- Removed PostHog sender and `CLITelemetryEvent`; cleaned up exports and
dead code in `telemetry/*`.
- Added `_capture_via_harness` that calls
`browser_harness.telemetry.capture_cli_event` if present, and
`_set_harness_client_env` to set `BH_CLIENT`/`BH_CLIENT_VERSION`.
- Added `_delegated_to_harness` to prevent double-reporting; only
capture when not delegated.
- Simplified CLI telemetry context by dropping agent/model detection,
task parsing, and mode tracking; kept only command name.

- **Migration**
- If you imported `browser_use.telemetry.CLITelemetryEvent` or
`capture_detached`, switch to
`browser_harness.telemetry.capture_cli_event`.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5165?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 10:51:02 -07:00
laithrw d1af39626e Merge branch 'main' into feat/delegate-cli-telemetry-to-harness 2026-07-07 10:48:36 -07:00
Laith Weinberger 8e279db893 global var 2026-07-07 10:38:35 -07:00
Laith Weinberger 803fd85f97 remove telemetry 2026-07-07 10:27:38 -07:00
laithrw d2c1535a58 improve CLI hint (#5164)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Clarifies the CLI legacy migration hint so users can upgrade to CLI 3.0
without confusion. The message now instructs to run `browser-use skill
install` to install the CLI 3.0 skill (replacing any existing
browser-use skill) and to load the skill after installation.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5164?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 10:11:08 -07:00
Laith Weinberger 55a56c31c4 improve CLI hint 2026-07-07 10:07:15 -07:00
laithrw 09813100a3 updated readme (#5162)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Simplified the README by replacing the LLM Quickstart steps with a
single link to llms-full.txt for coding agents, reducing clutter and
speeding setup.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5162?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-08 00:07:05 +08:00
laithrw 4c030a8e72 Merge branch 'main' into worktree-remove-llm-quickstart 2026-07-08 00:05:08 +08:00
laithrw 3c1d1c96ac rename browser harness -> use except urls (#5161)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Renamed all non-URL mentions of "browser-harness" to "browser-use" in
generated skill docs to prevent broken links and simplify the logic.

- **Refactors**
- Replaced multiple targeted string replacements with a single regex
that skips URL path segments (uses `re.sub(r'(?<!/)browser-harness',
'browser-use', body)`).

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5161?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-08 00:04:55 +08:00
Laith Weinberger 00552f5b64 updated readme 2026-07-07 09:04:08 -07:00
laithrw 53b1316076 Merge branch 'main' into skill-rename-regex 2026-07-08 00:02:34 +08:00
laithrw 90f1013cf6 Fix/cli slowdown (#5150)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Fixes CLI slowdowns by sending telemetry in a detached helper with a 5s
network timeout, and adds better error context via the stderr tail. Also
improves agent detection and sends a truncated task for easier
debugging.

- **Bug Fixes**
- Telemetry is sent via a detached helper to avoid blocking the CLI;
respects `ANONYMIZED_TELEMETRY` and uses a 5s send timeout.
- Error events include the last 500 chars of stderr as context when
available.

- **New Features**
- Broader agent client detection via env markers (e.g., Cursor, Copilot,
Claude Code, Gemini CLI, OpenClaw, Hermes, CodeX, Amp).
- Sends a truncated task (up to 20k chars) instead of fully redacting;
device ID is unified, persisted, and falls back to a machine
fingerprint.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5150?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 23:57:18 +08:00
laithrw 2d01ea316f Merge branch 'main' into fix/cli-slowdown 2026-07-07 23:48:25 +08:00
Laith Weinberger 61e069d1ed Merge branch 'fix/cli-slowdown' of https://github.com/browser-use/browser-use into fix/cli-slowdown 2026-07-07 08:47:48 -07:00
Laith Weinberger c04c724c97 timeout 2026-07-07 08:46:53 -07:00
laithrw d32d0bf05e reduce CLI repetition (#5160)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Condensed the README by removing the duplicated CLI setup snippet. The
“Browser Use CLI 3.0 is here.” headline now links to the CLI section
(`#-cli`) to centralize instructions and reduce clutter.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5160?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 23:35:26 +08:00
Laith Weinberger 95050db6ee reduce CLI repetition 2026-07-07 08:30:39 -07:00
Magnus Müller 052787f9c7 Update README.md (#5154) 2026-07-06 19:59:22 -07:00
Magnus Müller e352891498 Update README.md 2026-07-06 19:57:48 -07:00
Magnus Müller cc553f7cc3 Remove Template Quickstart section from README (#5153)
Removed the Template Quickstart section from the README.
2026-07-06 18:53:03 -07:00
Magnus Müller 0005c57c01 Remove Template Quickstart section from README
Removed the Template Quickstart section from the README.
2026-07-06 18:51:43 -07:00
Saurav Panda 52d67e7efe Fix visibility check mutating shared snapshot bounds (#5151)
## Problem

`is_element_visible_according_to_all_parents` mutated
`node.snapshot_node.bounds` **in place** while walking the frame chain
(`dom/service.py:295-296, 333-334`):

1. Every checked node's bounds were permanently shifted by frame offsets
and scroll, corrupting coordinates shared with `absolute_position` math,
paint-order filtering, and iframe hidden-element counting. The check
wasn't even idempotent — calling it twice on the same node could return
different results.
2. A frame node appears in **its own** frame chain
(`_construct_enhanced_node` appends it before computing its visibility),
so an iframe's bounds got offset by themselves — coordinates doubled. An
iframe at y=1200 on a 900px viewport (threshold 1000) became y=2400 →
classified invisible → `should_process_iframe=False` → its entire
content subtree silently dropped from extraction. A likely contributor
to "elements missing from the DOM" reports and one of the reasons
cross-origin iframe support has been flaky.

## Fix

Work on a copy of the bounds; skip `frame is node` in the chain walk. No
behavioral change for correctly-classified elements.

## Verification

New unit tests (no browser needed) that fail on the previous code:
- bounds object unchanged after a visibility check
- the check is idempotent
- an iframe checked against a frame chain containing itself is not
double-transformed (y=1200 stays visible)

Plus existing DOM serializer browser tests passing; pyright and ruff
clean.

Part of a series of small fixes from an internal review (#5133, #5146,
#5147, #5148).

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes a visibility bug that mutated shared bounds and double-offset
iframes. We now copy bounds and skip self in the frame chain, restoring
correct visibility and preventing dropped iframe content.

- **Bug Fixes**
- Copy `snapshot_node.bounds` before transforms to avoid in-place
mutations.
- Skip `frame is node` when walking the frame chain to prevent double
transforms.
- Add tests for non-mutation and iframe visibility; strengthen the
idempotency test to catch drift at threshold.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5151?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 09:34:26 +08:00
Saurav Panda 94ea8f60c8 Strengthen DOM visibility idempotency test 2026-07-06 18:27:04 -07:00
Laith Weinberger 425bcb8de3 rename browser harness -> use except urls 2026-07-06 17:52:00 -07:00
Saurav Panda 9863fe17fb Fix visibility check mutating shared snapshot bounds
is_element_visible_according_to_all_parents mutated snapshot_node.bounds in
place while walking the frame chain, permanently shifting every checked
node's coordinates by frame offsets and scroll — corrupting values shared
with absolute_position math, paint-order filtering, and any later visibility
check (the function was not even idempotent).

Worse, a frame node appears in its own frame chain (_construct_enhanced_node
appends it before computing visibility), so an iframe's bounds were offset
by themselves — coordinates doubled — wrongly classifying iframes past
half the viewport threshold as invisible and silently dropping their entire
content subtree from extraction.

Work on a copy of the bounds and skip self in the frame chain.
2026-07-06 17:40:46 -07:00
Saurav Panda 448cc10d62 Detect LLM output truncation instead of failing with misleading parse errors (#5148)
## Problem

When structured output hits the completion-token cap, the JSON arrives
cut mid-string. Nothing checked `finish_reason`/`stop_reason` anywhere
(`grep stop_reason browser_use/agent/service.py` → no hits), so users
saw either:

- an opaque `Unterminated string starting at...` / pydantic validation
error, or
- **worse**: a valid-but-chopped answer, if truncation landed between
JSON fields — silent partial answers.

With `ChatOpenAI`'s default `max_completion_tokens=4096`, this regularly
hits long `done(text=...)` and `extract` outputs. The real cause was
never surfaced.

## Fix

Each provider checks its truncation signal before parsing structured
output and raises a clear error naming the cause and the knob:

- **OpenAI** (`finish_reason == 'length'`): raise
`ModelProviderError('Model output was truncated at
max_completion_tokens=N; ... increase max_completion_tokens or request
shorter output')`
- **Anthropic** (`stop_reason == 'max_tokens'`): same, for the tool_use
input path; plus an `except ModelProviderError: raise` guard so the new
error isn't re-wrapped by the generic catch-all into a status-502 blob
- **Google** (`MAX_TOKENS` finish reason): shared
`_raise_if_output_truncated()` helper applied to both the structured
path and the fallback text path

Status code **400** is deliberate: it's not in any provider's retryable
set (Google retries 429/5xx), and retrying an identical request would
truncate identically.

## Verification

- Regression test drives the **real OpenAI SDK** against a local
httpserver returning `finish_reason='length'` with JSON cut mid-string —
asserts the error names the truncation and the parameter, not a parse
failure (fails on previous code with the misleading validation error). A
companion test confirms normal `finish_reason='stop'` responses still
parse.
- 9 LLM tests passing; pyright and ruff clean.

Anthropic/Google paths are verified by type-checker and code-trace;
their SDK transports don't point at a local httpserver as easily —
follow-up welcome if there's an established fake for them.

Part of a series of small fixes from an internal review (#5133, #5146,
#5147).

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Detect and surface LLM output truncation instead of misleading JSON
parse errors, and allow switching to a fallback LLM when truncation
occurs. Also handles reasoning models that return `content=null` with a
length-based finish reason.

- **Bug Fixes**
- OpenAI: If `finish_reason == 'length'`, raise
`ModelOutputTruncatedError` (400) before the missing-content guard;
reference `max_completion_tokens` or say "the model's output token
limit" when unset.
- Anthropic: If `stop_reason == 'max_tokens'`, raise
`ModelOutputTruncatedError`; add `except ModelProviderError: raise` so
the clear error isn’t re-wrapped.
- Google: Detect `MAX_TOKENS` in both structured and fallback paths via
`_raise_if_output_truncated`; raise (400) referencing
`max_output_tokens` or the generic phrase when unset.
- Agent: Introduced `ModelOutputTruncatedError` (400). Provider retry
loops skip it; the agent treats it as fallback-switchable so a
higher-cap model can succeed.
- Tests: Cover truncation vs normal parse, unset-cap message,
`content=None` case, and that truncation triggers a fallback LLM switch.

- **Refactors**
  - Trimmed comments to essentials.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5148?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 08:39:12 +08:00
laithrw 285ba3b401 Merge branch 'main' into fix/cli-slowdown 2026-07-07 08:29:32 +08:00
Laith Weinberger b1bf5281ad better error tracking 2026-07-06 17:28:02 -07:00
Laith Weinberger 9ee4929912 don't slow down CLI 2026-07-06 17:15:17 -07:00
Saurav Panda 248f98f208 Merge branch 'main' into fix/llm-output-truncation 2026-07-07 07:51:02 +08:00
Saurav Panda 2afef14667 Trim comments to essentials 2026-07-06 16:47:27 -07:00
laithrw 95ec7c634c shim for legacy CLI cmds (#5149)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Adds a legacy command shim to the `browser-use` CLI that detects pre-3.0
subcommands/flags and prints a short migration guide with examples. It
exits with code 2 and tags the run as `legacy:<cmd>` instead of running
the harness.

- **New Features**
- Detects common pre-3.0 commands and flags and maps them to clear
hints.
- Prints a structured migration message to stderr with stdin piping
examples, core helpers, and doc links.
- Exits with status 2 to signal misuse; avoids starting the browser
harness.
- Updates command context/dispatch to label events as `legacy:<cmd>` for
easier tracking.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5149?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 07:46:39 +08:00
Saurav Panda e672ab6de7 Detect truncation before the missing-content guard
OpenAI reasoning models can spend the entire max_completion_tokens budget on
hidden reasoning, returning finish_reason='length' with content=null. The
truncation check ran after the missing-content guard, so that case raised
the generic 'Failed to parse structured output' (500) instead of
ModelOutputTruncatedError — no truncation signal, no fallback switch. Check
finish_reason first; it does not depend on content.
2026-07-06 16:45:33 -07:00
Laith Weinberger b65642f810 shim for legacy CLI cmds 2026-07-06 16:40:41 -07:00
Saurav Panda db70a813c1 Let truncation errors switch to the fallback LLM
The 400 status chosen for truncation errors (deliberately outside provider
retry lists — an identical retry truncates identically) also fell outside
Agent._try_switch_to_fallback_llm's allowlist, so a configured fallback_llm
could no longer rescue a truncated run. Before this PR the downstream parse
failure was wrapped as a 502 ModelProviderError, which did allow the switch.

Introduce ModelOutputTruncatedError(ModelProviderError, status 400): the
three providers raise it, provider retry loops still skip it, and the
agent's fallback check treats it as switchable explicitly — a fallback with
a different output cap can succeed where the primary truncated.
2026-07-06 16:31:18 -07:00
Saurav Panda db2b2c67f9 Describe truncation gracefully when the token cap is unset
max_completion_tokens (OpenAI) and max_output_tokens (Google) are optional;
when set to None the truncation message printed 'truncated at
max_output_tokens=None'. Fall back to "the model's output token limit" —
a MAX_TOKENS/length finish reason means some server-side cap fired even
with no client-side cap configured. Anthropic's max_tokens is non-optional
and unaffected.
2026-07-06 15:56:03 -07:00
Saurav Panda 5f690c207e Detect LLM output truncation instead of failing with misleading parse errors
Structured output cut off at the completion-token cap was never detected:
OpenAI's finish_reason='length', Anthropic's stop_reason='max_tokens', and
Gemini's MAX_TOKENS finish reason all produced JSON cut mid-string, which
surfaced as an opaque parse error ('Unterminated string starting at...') —
or worse, a valid-but-chopped prefix. The actual cause (output token cap)
was never mentioned, and with defaults like max_completion_tokens=4096 this
regularly hits long done()/extract outputs.

Each provider now checks the finish/stop reason before parsing structured
output and raises a clear ModelProviderError ('Model output was truncated
at max_*_tokens=N; increase it or request shorter output'). Status code 400
is used deliberately: it is not in any retry list, and retrying the same
request would truncate identically.

Also adds an 'except ModelProviderError: raise' guard in the Anthropic
handler so the new error is not re-wrapped by the generic catch-all.
2026-07-06 15:17:52 -07:00
Saurav Panda 6313da7846 Preserve BrowserError's structured memory through execute_action (#5147)
## Problem

`Registry.execute_action`'s catch-all `except Exception` also caught
`BrowserError`, flattening it into a generic `RuntimeError('Error
executing action ...')`. That destroyed the structured
`short_term_memory` / `long_term_memory` the error carries specifically
to steer the LLM's next action — e.g. the list of available dropdown
options when the agent clicks a `<select>`.

The `except BrowserError` branch in `Tools.act` (which formats those
memories into an `ActionResult` via `handle_browser_error`) was dead
code for any action that lets a `BrowserError` propagate through
`execute_action` — `upload_file`, `dropdown_options` via
`event_result(raise_if_any=True)`, extraction handler errors. The LLM
got an opaque error string instead of actionable context, burning extra
steps rediscovering what the error already knew.

## Fix

One `except BrowserError: raise` ahead of the generic handlers in
`execute_action`, so `handle_browser_error` in `Tools.act` is the single
formatting point again.

## Verification

- New regression test: a registered action raising
`BrowserError(short_term_memory=..., long_term_memory=...)` must arrive
in the `ActionResult` with `error` = long-term memory and
`extracted_content` = short-term memory. Fails on the previous code with
the flattened generic string.
- 17 tools/registry tests passing; pyright and ruff clean.

Part of a series of small fixes from an internal review (same batch as
#5133, #5146).

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Preserves structured `BrowserError` memory through
`Registry.execute_action` while keeping plain errors recoverable, so
agents get context without breaking callers. `Tools.act` stays the
single formatting point for structured errors.

- **Bug Fixes**
- Re-raise `BrowserError` only when `long_term_memory` is present;
otherwise, flatten to `RuntimeError` to preserve previous recoverable
behavior.
- Map `long_term_memory` -> `ActionResult.error` and `short_term_memory`
-> `ActionResult.extracted_content` for structured errors.
- Add tests for both structured and plain `BrowserError` paths to lock
in behavior.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5147?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 06:17:04 +08:00
Saurav Panda 782535c34f Keep plain BrowserErrors recoverable: only structured ones bypass the generic handler
An unconditional re-raise sent BrowserErrors without long_term_memory (e.g.
upload_file's failure paths) into Tools.act's handle_browser_error, which
re-raises exactly those — escaping act() as an exception where callers
previously got a recoverable ActionResult(error=...).

Guard the bypass on long_term_memory being present (the exact condition
handle_browser_error formats without re-raising; short_term_memory alone
would still re-raise), and flatten plain BrowserErrors to RuntimeError as
before. Regression test covers the plain-BrowserError path through
tools.act.
2026-07-06 15:04:37 -07:00
Saurav Panda f30c3952c9 Preserve BrowserError's structured memory through execute_action
Registry.execute_action's catch-all handler flattened BrowserError into a
generic 'Error executing action ...' RuntimeError, destroying the structured
short_term_memory/long_term_memory the error carries to steer the LLM's next
action (e.g. the list of available dropdown options when clicking a select).
The 'except BrowserError' branch in Tools.act that formats those memories
into an ActionResult was dead code for any action that let a BrowserError
propagate (upload_file, dropdown_options via event_result, extraction
handlers).

Re-raise BrowserError before the generic handlers so handle_browser_error
becomes the single formatting point again.
2026-07-06 14:52:39 -07:00
Saurav Panda 4cd898212c Fix markdown extraction destroying URLs and dropping long link lines (#5146)
## Problem

Two content-destruction bugs in `extract_clean_markdown` caused
extraction results to silently lose content — a direct source of agents
reporting 'not found on page' for content that was there:

1. **All percent-encodings stripped**: `re.sub(r'%[0-9A-Fa-f]{2}', '',
content)` ran over the entire markdown output, corrupting every URL
containing `%20`/`%2F`/`%26`... — precisely when `extract_links=True`
was requested.
2. **Long link lines dropped**: the JSON-blob heuristic removed any line
over 100 chars starting with `{` **or `[`** — which matches long
markdown links `[text](very-long-url)`, clickable images
`[![alt](src)](href)`, and citation-style lines. Items simply vanished
from extracted lists.

## Fix

- Delete the `%XX` regex entirely (markdownify does not emit stray
percent-encodings; URLs must survive intact).
- Only drop long `{`/`[`-prefixed lines that actually parse as JSON
(`json.loads`), so SPA state blobs are still filtered while markdown
links survive. The three upstream regexes for common blob shapes are
unchanged.
- Extract the HTML→markdown conversion into a pure
`convert_html_to_markdown()` helper so this stage is unit-testable
without a browser.

Trade-off: JS object literals (unquoted keys) over 100 chars are now
kept since they aren't valid JSON — deliberately erring toward never
destroying real content.

## Verification

- New regression tests: long markdown link lines and clickable-image
lines survive; valid JSON array blobs are still dropped; percent-encoded
URLs survive the full HTML→markdown pipeline. The link-line test fails
on the previous code.
- 37 markdown extractor/chunking tests passing; pyright and ruff clean.

Part of a series of small fixes from an internal review (same batch as
#5133).

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes content loss in markdown extraction by preserving percent-encoded
URLs and keeping long markdown link/image lines. Prevents broken links
and “not found on page” misses during link extraction.

- **Bug Fixes**
- Removed `%XX` stripping from the output so `%20`, `%2F`, `%26`, etc.
in URLs are preserved.
- Only drop long `{`/`[` lines if they parse as JSON; long `[text](url)`
and `[![alt](src)](href)` lines now remain.

- **Refactors**
- Extracted HTML→markdown into `convert_html_to_markdown()` for easier
testing and reuse.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5146?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 05:49:46 +08:00
Saurav Panda ab08dea62c Fix markdown extraction destroying URLs and dropping long link lines
Two content-destruction bugs in extract_clean_markdown:

- A cleanup regex stripped every %XX sequence from the converted markdown,
  corrupting all percent-encoded URLs (%20, %2F, ...) — precisely when
  extract_links=True was requested.
- The JSON-blob line filter dropped any line over 100 chars starting with
  '{' OR '[' — silently deleting long markdown links [text](long-url),
  clickable images, and citation-style lines.

Delete the %XX regex, and only drop long lines that actually parse as JSON
(json.loads) so SPA state blobs are still filtered while markdown links
survive. Also extract the HTML->markdown conversion into a pure
convert_html_to_markdown() helper so this stage is unit-testable.
2026-07-06 14:45:25 -07:00
Saurav Panda 68a11ef77d Fix navigation readiness detection: per-target lifecycle event storage (#5133)
## Problem

Every navigation could burn its full readiness timeout (3s same-domain /
8s cross-domain) and then proceed on a page in an unknown load state —
DOM snapshots and screenshots taken on half-loaded pages, plus 3-8s of
dead time per navigation.

**Root cause:** `_navigate_and_wait` polls a lifecycle-event deque that
was fed by a per-session closure registered via
`cdp_client.register.Page.lifecycleEvent(...)`. cdp-use's event registry
is **single-slot per CDP method** (`self._handlers[method] = callback`),
so every new target attach **replaced** the previous tab's handler.
Existing tabs' deques froze with only pre-navigation events, which the
loaderId filter then (correctly) rejected — guaranteed timeout.

This is deterministic with 2+ tabs, and nondeterministic even single-tab
because each target gets attached twice (manual `attachToTarget` +
browser-level `setAutoAttach`), creating two session objects with two
competing registrations while `_get_session_for_target` picks one
arbitrarily.

## Fix

- Lifecycle events are now stored **per target_id in SessionManager**,
fed by **one global** `Page.lifecycleEvent` handler registered once in
`start_monitoring()` and routed by `session_id` — same pattern as the
existing Target attach/detach handlers. Buffers are freed on target
removal.
- `_navigate_and_wait` reads the per-target buffer and returns a timeout
status string instead of swallowing readiness timeouts;
`on_NavigateToUrlEvent` surfaces it via
`NavigationCompleteEvent.loading_status`, so downstream consumers can
distinguish 'loaded' from 'gave up waiting'.
- loaderId-less lifecycle events that predate the current navigation are
skipped (defense against stale entries).
- Dropped unused `CDPSession._lifecycle_lock`.

## Verification

Deterministic regression test
(`tests/ci/browser/test_navigation_readiness.py`): navigate tab A after
opening tab B — **before: exactly 3.01s (burned timeout), after:
<0.5s**. Also covers: fast-page navigation completes without burning the
timeout, readiness timeout is reported via the return status (page with
a stalled `<img>` holding back `load`), and successful navigation
returns no timeout status.

- 4 new tests + 20 existing navigation/session/cross-origin tests
passing
- pyright and ruff clean

## Notes

- The loaderId filter itself was verified correct against live Chrome
(new-navigation events match `Page.navigate`'s returned loaderId within
ms) and is kept as-is.
- Known remaining issue (separate PR): HAR recording registers
`Page.lifecycleEvent` too and will clobber this global handler when
`record_har_path` is set — same single-slot registry problem, needs a
proper fan-out multiplexer.
- The double-attach (two CDP sessions per pre-existing target) is also a
separate follow-up; it's harmless to this wait loop now.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes navigation readiness by storing lifecycle events per target and
routing them through one global `Page.lifecycleEvent` handler. Also
short-circuits same-document navigations so fragment/History API changes
don’t burn the timeout.

- **Bug Fixes**
- Store lifecycle events per `target_id` in `SessionManager`, fed by a
single global `Page.lifecycleEvent` from `cdp-use`; clear buffers on
target removal.
- `_navigate_and_wait` reads the per-target buffer and returns a timeout
status string; surfaced via `NavigationCompleteEvent.loading_status`.
- Ignore stale signals: drop events with mismatched `loaderId`, and skip
loaderId-less events older than the navigation start.
- Treat same-document navigations (no `loaderId`) as complete
immediately.
- Regression tests: fast navigations finish without burning fallback
timeouts; navigating tab A remains fast after opening tab B; readiness
timeouts are reported; same-document navigation completes immediately.

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

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5133?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-07-07 05:41:48 +08:00