BrowserSession.clear_cookies() sends Network.clearBrowserCookies on the
root CDP client, which always fails:
RuntimeError: {'code': -32601,
'message': "'Network.clearBrowserCookies' wasn't found"}
Network is a per-target domain and is not dispatchable without a session
attachment; the root client has none. Storage is browser-level, and the
cookies() getter two lines above already uses Storage.getCookies() there.
Measured against a live CDP endpoint:
Network.clearBrowserCookies() REJECTED -32601
Storage.clearCookies() OK
Storage.clearCookies(session_id=...) OK
Network.clearBrowserCookies(session_id=...) OK
The private _cdp_clear_cookies() already uses Storage.clearCookies with a
session_id, though its docstring still refers to Network.clearBrowserCookies
- this looks like the public method was missed when that one was fixed.
Page.navigate omits loaderId for same-document navigations (#fragment,
History API), and Chrome emits no new load/DOMContentLoaded lifecycle events
for them — the navigation is already committed when Page.navigate returns.
The stale-event timestamp guard would otherwise reject all buffered events
and burn the full readiness timeout.
Short-circuit when loaderId is absent, and simplify the stale-event guard
(the no-navigation-id case can no longer reach it). Regression test drains
the previous load's trailing networkIdle first so a stale event can't
accidentally satisfy the wait.
Navigation waits polled a per-session event deque whose feeding handler was
registered per-session on cdp-use's single-slot event registry. Any later
target attach replaced the handler, freezing existing tabs' deques with only
pre-navigation events, so every navigation on those tabs burned the full
readiness timeout (3s same-domain / 8s cross-domain) and then proceeded on a
page in unknown load state.
- Store lifecycle events per target_id in SessionManager, fed by ONE global
Page.lifecycleEvent handler registered in start_monitoring() and routed by
session_id; buffers are freed on target removal
- _navigate_and_wait reads the per-target buffer and now returns a timeout
status string instead of swallowing readiness timeouts;
on_NavigateToUrlEvent surfaces it via NavigationCompleteEvent.loading_status
- Skip loaderId-less lifecycle events that predate the current navigation
- Drop unused CDPSession._lifecycle_lock
Deterministic regression test: navigating tab A after opening tab B took
exactly the 3s fallback timeout before this fix, <0.5s after.
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.
When a user passes proxy_country_code=None to BrowserSession, they
intend to disable the proxy. Previously, None was indistinguishable
from "not passed" because both used None as the default value. The
server then applied its default (US proxy) instead of disabling it.
Introduces an _UNSET sentinel to distinguish "not passed" (server
default) from "explicitly None" (no proxy). When proxy_country_code
is explicitly set to None, it is now included in the CreateBrowserRequest
and sent as null in the API body, which the server correctly interprets
as "disable proxy".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cdp_use.CDPClient.send_raw awaits a future that only resolves when the
browser sends a response with a matching message id. There is no timeout
on that await. Against the cloud browser service, the failure mode we
observed is: WebSocket stays alive at the TCP/keepalive layer (proxy
keeps pong-ing our pings), but the browser upstream is dead / unhealthy
and never sends any CDP response. send_raw's future never resolves, and
every higher-level timeout in browser-use (session.start's 15s connect
guard, agent.step_timeout, tools.act's action timeout) relies on
eventually getting a response — so they all wait forever too.
Evidence from a 170k-task collector run: 1,090 empty-history traces,
100% hit the 240s outer watchdog, median duration 582s, max 2214s, with
cloud HTTP layer clean throughout (all 200/201). One sample showed
/json/version returning 200 OK and then 5 minutes of total silence on
the WebSocket before forced stop — classic silent-hang.
Fix: add TimeoutWrappedCDPClient, a thin subclass of cdp_use.CDPClient
that wraps send_raw in asyncio.wait_for(timeout=cdp_request_timeout_s).
Any CDP method that doesn't respond within the cap raises plain
TimeoutError, which propagates through existing `except TimeoutError`
handlers in session.py / tools/service.py. Uses the same defensive env
parse pattern as BROWSER_USE_ACTION_TIMEOUT_S — rejects empty /
non-numeric / nan / inf / non-positive values with a warning fallback.
Default is 60s: generous for slow operations like Page.captureScreenshot
or Page.printToPDF on heavy pages, but well below the 180s step timeout
and any typical outer watchdog. Override via BROWSER_USE_CDP_TIMEOUT_S.
Wired into both CDPClient construction sites in session.py (initial
connect + reconnect path). All 17 existing real-browser tests
(test_action_blank_page, test_multi_act_guards) still pass.
- 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
BrowserSession.on_BrowserStopEvent calls stop_browser() but never calls
_cloud_browser_client.close(), leaving the httpx connection pool alive.
On Lambda provisioned concurrency, these pools accumulate across
invocations — memory climbs from ~1.3GB to the 3GB ceiling over hours,
triggering OOM kills (21 Runtime.ExitError crashes in 6 hours observed
in production).
Changes:
- Call _cloud_browser_client.close() in on_BrowserStopEvent after
stop_browser completes (in a finally block so it runs even if
stop_browser fails)
- Make CloudBrowserClient.close() idempotent (check is_closed before
calling aclose) so it's safe to call multiple times
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add nav_timeout parameter to navigate_to_url_and_wait() method
- Pass nav_timeout from NavigateToUrlEvent to control CDP navigation timeout
- Fix issue where TIMEOUT_NavigateToUrlEvent env var only controlled outer timeout but CDP call had hardcoded 20s timeout
- Add comprehensive test case to verify env var controls CDP timeout
- Updated the BrowserSession class to ensure the User-Agent header is set correctly for both local and remote connections.
- Introduced a utility function to retrieve the browser-use version and incorporated it into the User-Agent string.
- Ensured headers are consistently handled as dictionaries to avoid potential issues with None values.
This change improves the reliability of the CDP client communication by providing a proper User-Agent, which can be crucial for certain web interactions.
- Add `browser-use upload <index> <path>` command for uploading files to
file input elements via the CLI
- Extract find_file_input_near_element from nested closures in tools/service.py
to a reusable method on BrowserSession, deduplicating two copies
- Add BrowserWrapper.upload() for the Python REPL
- Resolve file paths to absolute on the client side before sending to daemon
- Update SKILL.md files and README with upload command docs