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
The circuit breaker and agent error handler were using a hardcoded 20s
timeout, but the worst-case auto-reconnect window is ~48s (3 attempts *
15s + 7s delays). Handlers would raise ConnectionError while reconnection
was still in progress. Now both use BrowserSession.RECONNECT_WAIT_TIMEOUT
(54s) derived from the actual reconnect parameters.
Address review feedback: trust_env=False now only applies to localhost/127.0.0.1/::1.
Remote CDP URLs will still respect HTTP_PROXY/HTTPS_PROXY environment variables.
On Windows with HTTP_PROXY/HTTPS_PROXY env vars set, httpx respects
these for localhost requests, causing 502 responses when querying
the CDP /json/version endpoint.
Adding trust_env=False prevents httpx from using proxy env vars,
ensuring localhost CDP communication works regardless of system
proxy configuration.
- auto-detects chrome executable and user data dir on macos/windows/linux
- auto-selects first available profile if none specified
- added Browser.list_chrome_profiles() to see available profiles
- updated examples to prompt user for profile selection
- fixed missing browser.stop() in save_cookies.py