fix(browser): browser_exec uses the local fallback when the cloud browser provider fails

When the configured cloud browser provider fails to create a session (seen live:
Browser Use gateway HTTP 429 RATE_LIMIT_EXCEEDED), the session layer already falls
back to local Chromium and marks the session fallback_from_cloud, which keeps the
built-in browser_* tools working. browser_exec read only cdp_url from that session,
found none (local sessions launch lazily), and failed the whole call with "returned
no CDP endpoint, so Browser Use mode cannot drive it".

On a fallback session, resolve the packaged Chromium's CDP endpoint the same way the
no-provider path does, and log the provider failure once.
This commit is contained in:
Teknium committed 2026-10-10 23:51:59 -07:00
1 parent 0faf0938eb
commit 04eb00cef7
2 files changed
+25 -1

No files matched your search

+12
View File
@@ -483,6 +483,18 @@ class TestBackendCdpResolution:
err = bu_cli._resolve_backend_cdp(self._env(), "t1")
assert err and "api down" in err
def test_provider_fallback_to_local_drives_packaged_chromium(self, monkeypatch, _fake_managed_chromium):
"""A cloud failure the session layer already fell back from (429, quota) must drive the local
Chromium it chose, not fail the call as 'no CDP endpoint'."""
monkeypatch.setattr("tools.browser_tool_cdp._get_cdp_override", lambda: "")
monkeypatch.setattr(bt_cloud, "_get_cloud_provider", lambda: object())
monkeypatch.setattr(bt_session, "_get_session_info", lambda task_id: {
"cdp_url": None, "features": {"local": True}, "fallback_from_cloud": True, "fallback_reason": "429"})
env = self._env()
assert bu_cli._resolve_backend_cdp(env, "t1") is None
assert env["BU_CDP_WS"] == "ws://127.0.0.1:47000/devtools/browser/t1"
assert _fake_managed_chromium == [("t1", "get", ("cdp-url",))]
def test_provider_without_cdp_returns_error(self, monkeypatch):
monkeypatch.setattr("tools.browser_tool_cdp._get_cdp_override", lambda: "")
+13 -1
View File
@@ -502,13 +502,25 @@ def _resolve_backend_cdp(env: dict, task_id: Optional[str], session_name: str =
return None
provider_name = type(provider).__name__
session: dict = {}
def _session_info(key: str) -> dict:
session.update(_get_session_info(key) or {})
return session
err = _export_session_cdp(
env, _get_session_info, _backend_cache_key(task_id, session_name),
env, _session_info, _backend_cache_key(task_id, session_name),
lambda e: (f"Cloud browser provider {provider_name} failed to provide a session: {e}. "
"Fix the provider configuration or switch backends via `hermes tools` → Browser Automation."),
f"Cloud browser provider {provider_name} returned no CDP endpoint, so Browser Use mode "
"cannot drive it. Switch to the built-in browser tools for this provider.",
)
if err and session.get("fallback_from_cloud"):
# The provider failed (rate limit, quota, outage) and the session layer fell back to local
# Chromium, as it does for the built-in tools; that session has no CDP URL until it launches.
logger.warning("browser_exec: %s failed (%s); using local Chromium for this session",
provider_name, session.get("fallback_reason"))
return _resolve_managed_chromium_cdp(env, task_id, session_name)
# A provider browser keyed bu-named-<name> is exclusive to this session — the
# own-tab preamble would just leak a blank tab into it.
if err is None and session_name: