## Problem
The V2 worker reuses a `keep_alive` `BrowserSession` across warm Lambda
invocations. At the end of a run, `Agent.close()` stops the session's
event bus and nulls out its async primitives (`event_queue` /
`_on_idle`) to release the event loop (the "fix keep-alive hang"
change).
On a warm resume, the worker can `step()` that bus *before* any
`dispatch()` restarts it. In that torn-down state, stock `bubus`
`EventBus.step()` hits:
```
AssertionError: EventBus._start() must be called before step()
```
The crash is deterministic (not transient), so retries don't recover and
the task dead-letters after max receives — ~1,600+ occurrences over 2
days per the DLQ.
## Root cause
The nulling in `Agent.close()` is *load-bearing*: it's what lets the
next `dispatch()` recreate a fresh queue and `_start()` the bus again
(bubus `_start()` only recreates the queue when `event_queue is None`).
So we can't simply stop nulling — but a nulled bus is exactly the state
where `step()` / `wait_until_idle()` assert.
## Fix
Wrap the session's event bus in a small `ResilientEventBus(EventBus)`
subclass whose `step()` and `wait_until_idle()` are **safe no-ops when
the bus has not been started**, instead of asserting. Teardown is
unchanged, so a later `dispatch()` still recreates the queue and
restarts the bus normally.
This is the browser-use-level equivalent of the issue's suggested fix#1
("patch bubus so `step()` tolerates a stopped/unstarted bus"), scoped to
the bus that actually gets torn down.
## Verification
`tests/ci/test_event_bus_resilience.py`:
- the session's default `event_bus` is a `ResilientEventBus`
- `step()` / `wait_until_idle()` on a stopped + nulled bus return `None`
(no `AssertionError`)
- a torn-down bus still restarts and processes events on the next
`dispatch()`
- a stock `EventBus` still reproduces the original assertion (documents
the upstream bug)
All 4 pass; `pyright` and `ruff` clean.
Fixes ENG-5280.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Tolerate stepping a torn-down event bus on warm-Lambda resumes to
prevent deterministic worker crashes and DLQs. The session now uses a
resilient event bus that no-ops until the next `dispatch()` restarts it.
Fixes ENG-5280.
- **Bug Fixes**
- Added `ResilientEventBus(EventBus)` that makes `step()` and
`wait_until_idle()` no-ops when `_on_idle` or `event_queue` is `None`.
- Made `BrowserSession.event_bus` use `ResilientEventBus` by default and
in `kill()`/`stop()`, and kept default bus names as `EventBus_*`
(explicit names still honored).
- Added `tests/ci/test_event_bus_resilience.py` to verify no-op
behavior, restart on `dispatch()`, name prefix preservation, and to
document the upstream `bubus.EventBus` assertion.
- Tightened `ResilientEventBus` docstrings and comments (no behavior
change).
<sup>Written for commit 5123e4fc08.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5086?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. -->
bubus derives the default bus name from the class name, so the
ResilientEventBus default factory changed session bus names from
EventBus_* to ResilientEventBus_*, breaking the EventBus_ prefix contract
asserted in tests/ci/browser/test_session_start.py. Default the subclass
name back to EventBus_<id> when none is given (explicit names still honored).
The V2 worker reuses a keep_alive BrowserSession across warm Lambda
invocations. At the end of a run Agent.close() stops the session's event
bus and nulls out its async primitives (event_queue / _on_idle) to release
the event loop. On resume the worker can step() the bus before any
dispatch() restarts it, and stock bubus EventBus.step() asserts
"EventBus._start() must be called before step()" in that state — crashing
the run deterministically so the task dead-letters after max receives
(~1,600+ occurrences over 2 days).
Wrap the session's bus in a ResilientEventBus subclass whose step() and
wait_until_idle() are safe no-ops when the bus has not been started,
instead of asserting. The nulling stays (it's what lets the next dispatch()
recreate a fresh queue and _start() the bus), so a later dispatch() still
restarts the bus and processes events normally.
Fixes ENG-5280.
## What
The `models/test_llm_anthropic` CI job is failing on every branch
(including unrelated PRs) with:
```
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error':
{'type': 'not_found_error', 'message': 'model: claude-sonnet-4-0'}, ...}
```
`tests/ci/models/test_llm_anthropic.py::test_anthropic_claude_sonnet_4_0`
runs a real agent loop against the live Anthropic API and pins the model
id `claude-sonnet-4-0` (Claude Sonnet 4, `claude-sonnet-4-20250514`).
Anthropic **retired Sonnet 4 on 2026-06-15**, so the request now 404s,
the agent exhausts its retries without ever clicking the button, and the
test fails its final assertion (`Button was not clicked`).
Example failing run:
https://github.com/browser-use/browser-use/actions/runs/28216634342/job/83589007682
## How
Bump the test to the current GA Sonnet, `claude-sonnet-4-6` (Anthropic's
drop-in replacement for the retired alias), and rename the test function
to match.
## Notes / follow-up (not in this PR)
Other references to the retired `claude-sonnet-4-0` alias exist but
don't make live API calls, so they don't break CI today:
- `browser_use/llm/models.py` — the `anthropic_claude_sonnet_4_0`
factory entry still maps to the retired alias, so
`get_llm_by_name('anthropic_claude_sonnet_4_0')` returns a model that
404s at runtime. Repointing vs. renaming that public key is a product
decision, left out of this PR.
- `tests/ci/models/test_llm_model_factory.py`,
`tests/ci/test_coordinate_clicking.py`, `tests/ci/test_beta_agent.py` —
string/registry fixtures (no network).
- `examples/models/claude-4-sonnet.py`,
`examples/features/fallback_model.py`, `AGENTS.md`,
`skills/open-source/references/quickstart.md` — examples/docs.
More broadly, the live-API model tests pin retiring `*-4-0` aliases, so
they break on Anthropic's retirement cadence; worth a follow-up to pin
only current GA aliases or mark these tests non-blocking.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Replace retired Anthropic model in the live API test from
`claude-sonnet-4-0` to `claude-sonnet-4-6` to stop 404s and get the
`models/test_llm_anthropic` job green again.
- **Bug Fixes**
- Updated `tests/ci/models/test_llm_anthropic.py`: renamed test and set
`model_name` to `claude-sonnet-4-6`.
- Resolves 404 `not_found_error` after Sonnet 4 retirement (2026-06-15).
<sup>Written for commit e29cdeae06.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5085?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. -->
Anthropic retired Claude Sonnet 4 (claude-sonnet-4-0 /
claude-sonnet-4-20250514) on 2026-06-15. The live-API model test in
tests/ci/models/test_llm_anthropic.py pinned that alias, so every CI run
now gets a 404 not_found_error and the agent never completes the task,
failing the models/test_llm_anthropic job on all branches.
Bump the test to the current GA Sonnet (claude-sonnet-4-6).
## What
Customers need the printed PDF to show page metadata — the date in the
header and the page URL in the footer. The `save_as_pdf` tool previously
called CDP `Page.printToPDF` with `displayHeaderFooter` unset, so no
metadata was ever rendered.
This makes `save_as_pdf` print metadata into the PDF margins **by
default**, mirroring what Chrome's own Print dialog does (the "Headers
and footers" checkbox is on by default there too):
- **Header:** the date (right-aligned)
- **Footer:** the page URL (left) + `page / totalPages` (right)
## How
- Added three params to `SaveAsPdfAction`:
- `display_header_footer: bool = True` — toggle the whole feature (set
`False` for a clean PDF).
- `header_template: str | None` / `footer_template: str | None` —
optional custom HTML, using Chrome's magic classes (`date`, `title`,
`url`, `pageNumber`, `totalPages`).
- When enabled, pass `displayHeaderFooter` + header/footer templates and
explicit margins to `Page.printToPDF`. Two gotchas handled:
- Chrome defaults header/footer font-size to **0px**, so the default
templates set an explicit `font-size`.
- Chrome **clips** the header/footer unless the page leaves vertical
room, so explicit top/bottom margins are set when the feature is on.
## Testing
`tests/ci/test_action_save_as_pdf.py` (12 passing), including new tests
that:
- Extract text from the rendered PDF with `pypdf` and assert the page
URL **actually appears** in the footer.
- Assert the URL is **absent** when `display_header_footer=False`.
- Assert custom header/footer templates are honored.
Backward compatibility: existing PDFs remain valid; only the (previously
empty) margins now carry metadata.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
`save_as_pdf` now prints page metadata by default, matching Chrome’s
Print dialog: date in the header; URL and page numbers in the footer.
You can turn it off or provide custom header/footer HTML.
- **New Features**
- Added `display_header_footer` (default True) to toggle metadata.
- Added `header_template` and `footer_template` for custom HTML using
Chrome classes (`date`, `title`, `url`, `pageNumber`, `totalPages`).
- Sends `displayHeaderFooter`, templates, and explicit margins to
`Page.printToPDF` to prevent clipping; default templates set a font size
so text renders.
- **Bug Fixes**
- Default footer now truncates long URLs with ellipsis so page numbers
stay visible (adds a long-URL regression test).
<sup>Written for commit 16ce84f948.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5015?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. -->
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Adds the `qa` skill to QA-test websites and web apps on a real cloud
browser and return a 1–5 score with evidence and a pass/fail verdict.
Works with public URLs and `localhost` via an automatic tunnel, and
tears down tunnels on every path (including v2).
- **New Features**
- Visual verdict with a 1–5 score; the judge’s pass/fail is
authoritative.
- One flow: drive `browser-harness`; many flows: parallel v2 agents with
`judge` + structured output and a live dashboard link. If vision isn’t
available, always use v2 (even for one flow).
- Teardown closes the localhost tunnel for v2 runs too.
- **Dependencies**
- Auto-installs `browser-harness` and uses the stored
`BROWSER_USE_API_KEY` for v2 calls; no local-browser setup.
- For `localhost`, tunnel with `ngrok` (`--host-header=rewrite` +
skip-warning; BU proxy off) or `cloudflared`.
<sup>Written for commit cca6f65d72.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5074?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. -->
Teardown previously claimed only the one-flow/Claude paths had anything
to stop, but v2 runs against localhost also start a tunnel that was left
orphaned. Tear down the tunnel on every path that opens one.
Addresses cubic review comment on SKILL.md teardown step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A flex item defaults to min-width:auto and won't shrink below its content,
so a long/unbroken URL in the default footer template overflowed and pushed
the page / totalPages span off the printable area. Give the url span
min-width:0 + overflow ellipsis so it truncates, and flex-shrink:0 on the
page-count span so it always renders. Adds a long-URL regression test.
Added instructions for running the Browser Use agent in the terminal and
Python script.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Added quick-start steps to the README for running the Browser Use agent
from the terminal and in a Python script. Includes the `uv add
"browser-use[core]"` install command, the `browser` CLI invocation, and
a minimal Python example.
<sup>Written for commit bbb8a0ccc1.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5037?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. -->
## What
Removes the `bu-3` and `bu-3-max` model ids everywhere they were
surfaced, and switches the beta agent's default example to
`openai/gpt-5.5` (routed through the Browser Use gateway via the
provider-prefixed support added in ENG-5060).
## Changes
**Remove bu-3 / bu-3-max**
- `ChatBrowserUse`: dropped from the `bu_aliases` validation list (the
error message auto-updates). Provider-prefixed ids like `openai/gpt-5.5`
are still accepted.
- `browser_use/tokens/custom_pricing.py`: removed both pricing entries.
- `README.md`: removed the bu-3 / bu-3-max pricing blocks from the FAQ.
- Docstrings: removed the bu-3 / bu-3-max lines from the
`ChatBrowserUse` model options.
**Default example → `openai/gpt-5.5`**
- README quickstart and `examples/beta_agent/basic.py` now use
`ChatBrowserUse(model='openai/gpt-5.5')`, with
`ChatBrowserUse(model='bu-2-0')` shown as a commented alternative.
- `ChatBrowserUse` class docstring usage example updated likewise.
**Tests**
- Removed `test_accepts_bu3_models` (test_llm_retries) and
`test_custom_browser_use_pricing_includes_bu3_models`
(test_openrouter_token_cost).
- Dropped `bu-3` / `bu-3-max` from the accepted-aliases parametrize in
`test_llm_browseruse.py`.
- Swapped the beta-agent credential-bridging test from `bu-3-max` to
`bu-2-0`.
## Notes
- The OSS model `browser-use/bu-30b-a3b-preview` is untouched (distinct
from bu-3).
- Verified: `uv run pyright` clean; affected CI tests pass
(`test_llm_browseruse`, `test_llm_retries`,
`test_openrouter_token_cost`, `test_beta_agent` credential test).
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Remove `bu-3` and `bu-3-max` everywhere and set the beta agent example
to `openai/gpt-5.5` via the gateway, aligning with provider-prefixed
routing from ENG-5060. Updates docs, pricing, validation, and tests to
reflect the new defaults.
- **Refactors**
- Stop accepting `bu-3`/`bu-3-max` in `ChatBrowserUse`; keep
`bu-latest`, `bu-1-0`, `bu-2-0`, and provider-prefixed IDs.
- Remove custom pricing and README pricing blocks for `bu-3`/`bu-3-max`.
- Update README, examples, and docstrings to use `openai/gpt-5.5` (show
`bu-2-0` as an alternative).
- Drop tests for `bu-3`/`bu-3-max`; switch beta-agent credential test to
`bu-2-0`.
- Note: `browser-use/bu-30b-a3b-preview` is unchanged.
- **Migration**
- Replace `bu-3`/`bu-3-max` with `openai/gpt-5.5` or `bu-2-0`.
- Provider-prefixed IDs (`openai/*`, `anthropic/*`, `google/*`,
`browser-use/*`) continue to work through the gateway.
<sup>Written for commit 47a52790a6.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5020?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
Drop the bu-3 and bu-3-max model ids everywhere they were surfaced:
- ChatBrowserUse no longer lists them as valid bu-* aliases (provider-prefixed
ids like openai/gpt-5.5 are still accepted by the gateway).
- Remove their custom pricing entries and the README pricing blocks.
- Update docstrings, README quickstart, and the beta_agent example to use
openai/gpt-5.5 as the default, with bu-2-0 shown as a commented alternative.
- Drop the tests that asserted bu-3/bu-3-max acceptance and pricing.
## What
`ChatBrowserUse` now accepts **provider-prefixed model ids** —
`anthropic/*`, `openai/*`, `google/*` — alongside the existing `bu-*`
aliases and `browser-use/*` models. A single `BROWSER_USE_API_KEY` can
reach them:
```python
from browser_use import Agent, ChatBrowserUse
llm = ChatBrowserUse(model='anthropic/claude-sonnet-4-6') # or 'openai/gpt-5.5', 'google/gemini-3-pro'
agent = Agent(task='...', llm=llm)
```
Closes ENG-5060.
## Changes
- `browser_use/llm/browser_use/chat.py` — model validation accepts a
`bu-*` alias or any provider-prefixed id (`provider/model`); bare ids
like `gpt-5` are still rejected with guidance toward the
`provider/model` form. `bu-*` aliases and the `bu-latest` → `bu-2-0`
normalization are unchanged.
- `browser_use/agent/service.py` — the Claude Sonnet
`llm_screenshot_size` auto-config now also matches provider-prefixed
Claude ids (e.g. `anthropic/claude-sonnet-4-6`), so it isn't lost when
Claude is reached via `ChatBrowserUse`.
- Example, README FAQ entry, and tests.
## Test plan
- `uv run pytest tests/ci/models/test_llm_browseruse.py` — passing (1
skipped without an API key).
- `uv run pyright` — clean.
- pre-commit — passing.
## What this adds
A two-person approval gate on the PyPI publish job. Minimal change,
preserves the existing release flow.
### Before
- Maintainer creates a GitHub Release → `publish_to_pypi` runs
unattended → published to PyPI.
- Anyone with workflow-dispatch access (or a leaked token with workflow
scope) could trigger publish single-handedly.
### After
- Maintainer creates a GitHub Release → `publish_to_pypi` starts →
**pauses for approval** from a reviewer who is NOT the run actor
(gregpr07/MagMueller/sauravpanda, prevent_self_review:true).
- Approver clicks approve → publish runs unchanged.
## What's added
1. `environment: release` on the `publish_to_pypi` job.
2. Fail-closed preflight step that verifies the env still has required
reviewers and `prevent_self_review: true`. Same check the sdk pipeline
uses — guards against silent env config drift.
3. `actions: read` permission on the publish job for the env check API
call.
## What's NOT changed
- `tag_pre_release` job (RC bump flow) — unchanged.
- Triggers (`release: published` + `workflow_dispatch`) — unchanged.
- `uv publish --trusted-publishing always` — unchanged.
- `Push to stable branch` step — unchanged.
## Setup already live on the repo
- `release` environment created with 3 reviewers +
prevent_self_review:true
- `Protect main` ruleset tightened: squash-only + workflow-guardians
required_reviewers on `.github/workflows/**`
- Stale `PYPI_API_TOKEN` secret deleted (trusted-publishing supersedes)
## PyPI side note
The existing PyPI trusted publisher entry for `browser-use` does NOT
have an Environment configured. After this PR merges and before the next
release, the entry needs `Environment: release` added at
https://pypi.org/manage/project/browser-use/settings/publishing/ —
otherwise the OIDC token won't carry the env claim PyPI expects.
## Followups (NOT in this PR)
- `docker.yml` ships an image to Docker Hub on the same `release:
published` event using static `DOCKER_USERNAME`/`DOCKER_PASSWORD`.
That's a parallel publish vector that this PR does NOT close. Should be
gated too in a separate PR.
- `TRIGGER_CLOUD_BUILD_GH_KEY` is a static cross-repo PAT that should
migrate to fine-grained ≤30d (already on the followup list).
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds a two-person approval gate to the `publish_to_pypi` workflow by
gating it behind the protected `release` environment. Implements
ENG-4762 without changing triggers or the publish command.
- **New Features**
- Gate `publish_to_pypi` with `environment: release` to require approval
from a reviewer other than the actor (`prevent_self_review`).
- Add a fail-closed preflight that verifies required reviewers and
`prevent_self_review: true` on the environment.
- Grant `actions: read` to the job for the env check.
- **Migration**
- In PyPI trusted publisher for `browser-use`, set Environment to
`release` before the next release to ensure the OIDC token includes the
env claim.
<sup>Written for commit 1186d4cc44.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5013?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
The save_as_pdf action now renders page metadata into the PDF margins by
default, matching Chrome's Print dialog: the date in the header and the page
URL plus page numbers in the footer.
- Add display_header_footer (default True), header_template, and
footer_template params to SaveAsPdfAction.
- Pass displayHeaderFooter + header/footer templates and explicit margins to
CDP Page.printToPDF so the metadata has room to render (Chrome clips it
otherwise, and defaults header/footer font-size to 0px).
- Default templates show the date / URL + page numbers; callers can override
with custom HTML or disable entirely for a clean PDF.
browser-use ships to PyPI via OIDC trusted-publishing today, but the
publish job had no environment gate — anyone with workflow-dispatch
access (or any token with workflow scope) could trigger a publish
single-handedly. Adds:
- environment: release on publish_to_pypi
Reviewers configured separately on the env: gregpr07, MagMueller,
sauravpanda. prevent_self_review:true so the dispatcher cannot
self-approve.
- Fail-closed preflight that verifies the env still has required
reviewers and prevent_self_review enabled before publishing.
Same check the sdk pipeline uses. Guards against silent env
config drift.
- actions: read permission on the publish job for the env check.
Does NOT change the trigger model (release:published + workflow_dispatch),
does NOT touch tag_pre_release, does NOT touch the trusted-publishing
publish command itself. Minimal change, single security win:
two-person rule on every PyPI release.
Refs ENG-4762.
ChatBrowserUse now accepts provider-prefixed model ids (anthropic/*,
openai/*, google/*) alongside the bu-* aliases and browser-use/* models,
so a single BROWSER_USE_API_KEY can reach them. bu-* aliases and the
bu-latest -> bu-2-0 normalization are unchanged; bare ids are rejected
with guidance toward the provider/model form.
Also matches provider-prefixed Claude Sonnet ids in the Agent
llm_screenshot_size auto-config, so the screenshot optimization isn't
lost when Claude is reached via ChatBrowserUse.
Adds an example, a README FAQ entry, and tests.
ENG-5060
## Summary
- add BU3 and BU3max as accepted ChatBrowserUse model ids
- update the README first-agent example to use BU3max
- update public BU3 and BU3max token pricing
- bridge Browser Use gateway credentials from the beta Rust-core wrapper
into terminal env
## Validation
- `uv run pytest tests/ci/test_llm_retries.py::TestChatBrowserUseRetries
tests/ci/test_beta_agent.py::test_beta_agent_bridges_llm_credentials_to_terminal_env
-q`
- `SKIP=pyright uv run pre-commit run --files README.md
browser_use/llm/browser_use/chat.py browser_use/beta/service.py
tests/ci/test_llm_retries.py tests/ci/test_beta_agent.py`
## Notes
- Existing ChatBrowserUse defaults remain unchanged.
- Full pyright was not run; the targeted pre-commit run skipped pyright
as before.