From 643b3f450df1c6c884b2de8d0832d0af9b6ed272 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sun, 13 Sep 2026 12:20:17 -0700 Subject: [PATCH] fix(tools): stop check_fns swallowing resolver crashes into "returned False" check_vision_requirements (and five siblings: browser_vision, image/video generation, x_search, browser_vault) wrapped their whole probe in `except Exception: return False`. The registry then logged "returned False", indistinguishable from an unconfigured backend, and the only diagnostic for a crashed resolver was gone (#87950: named custom provider lookup failing in a long-lived multi-profile process, reported as vision tools silently vanishing). The registry owns the verdict: _run_check_fn_uncached and _check_fn_cached both catch, log with traceback, and return False. Let the exception reach them. Behaviour for the model is unchanged (tool hidden either way); agent.log now says why. --- tests/tools/test_vision_tools.py | 12 ++++++++++++ tools/browser_tool_install.py | 5 +---- tools/browser_vault_tool.py | 13 +++++-------- tools/image_generation_tool.py | 7 ++----- tools/video_generation_tool.py | 7 ++----- tools/vision_tools.py | 15 +++++++-------- tools/x_search_tool.py | 5 +---- 7 files changed, 30 insertions(+), 34 deletions(-) diff --git a/tests/tools/test_vision_tools.py b/tests/tools/test_vision_tools.py index d27424c9fb..5cf4bcd026 100644 --- a/tests/tools/test_vision_tools.py +++ b/tests/tools/test_vision_tools.py @@ -540,6 +540,18 @@ class TestVisionRequirements: assert check_vision_requirements() is True + def test_resolver_crash_propagates_instead_of_reading_as_unconfigured(self, monkeypatch): + """The gate must not swallow resolver exceptions into False: the registry owns the + verdict and logs the traceback, so users can tell "misconfigured" from "crashed" (#87950).""" + import agent.auxiliary_client as aux + + def _boom(**kw): + raise RuntimeError("named custom provider lookup failed") + + monkeypatch.setattr(aux, "resolve_vision_provider_client", _boom) + with pytest.raises(RuntimeError, match="lookup failed"): + check_vision_requirements() + # --------------------------------------------------------------------------- # Local path forms: tilde expansion and file:// URIs diff --git a/tools/browser_tool_install.py b/tools/browser_tool_install.py index f4df1d2f4a..30172bf2a7 100644 --- a/tools/browser_tool_install.py +++ b/tools/browser_tool_install.py @@ -335,8 +335,5 @@ def check_browser_vision_requirements() -> bool: """ if not check_browser_requirements(): return False - try: - from tools.vision_tools import check_vision_requirements - except ImportError: - return False + from tools.vision_tools import check_vision_requirements return check_vision_requirements() diff --git a/tools/browser_vault_tool.py b/tools/browser_vault_tool.py index 63d0cae598..a32e630b54 100644 --- a/tools/browser_vault_tool.py +++ b/tools/browser_vault_tool.py @@ -42,14 +42,11 @@ def _check_vault_available() -> bool: """Schema-gate: the vault tools ride with the browser. An empty vault still needs browser_vault_save_login so the agent can offer to remember a login the first time it meets a form; hiding the tools until an item exists meant nobody ever discovered the feature.""" - try: - from tools.browser_tool_install import check_browser_requirements - from tools.browser_use_cli import is_browser_use_cli_mode - # check_browser_requirements() is False by design in Browser Use mode (browser_exec replaces the - # built-in surface); the vault serves both stacks. - return bool(is_browser_use_cli_mode() or check_browser_requirements()) - except Exception: - return False + from tools.browser_tool_install import check_browser_requirements + from tools.browser_use_cli import is_browser_use_cli_mode + # check_browser_requirements() is False by design in Browser Use mode (browser_exec replaces the + # built-in surface); the vault serves both stacks. + return bool(is_browser_use_cli_mode() or check_browser_requirements()) # --------------------------------------------------------------------------- diff --git a/tools/image_generation_tool.py b/tools/image_generation_tool.py index 7c0b8fc257..4476743008 100644 --- a/tools/image_generation_tool.py +++ b/tools/image_generation_tool.py @@ -540,11 +540,8 @@ def check_image_generation_requirements() -> bool: if configured is None: return False # Probe only the selected plugin: a cloud key alone must not opt a user into a paid backend. - try: - provider = _get_plugin_provider(configured) - return bool(provider and provider.is_available()) - except Exception: - return False + provider = _get_plugin_provider(configured) + return bool(provider and provider.is_available()) # --- Registry --- diff --git a/tools/video_generation_tool.py b/tools/video_generation_tool.py index 4f33309939..731bd88451 100644 --- a/tools/video_generation_tool.py +++ b/tools/video_generation_tool.py @@ -104,11 +104,8 @@ def _discovered_registry(): def check_video_generation_requirements() -> bool: """True when at least one registered provider reports available.""" - try: - registry_mod, _ = _discovered_registry() - return any(_provider_call(p, "is_available", False) for p in registry_mod.list_providers()) - except Exception: - return False + registry_mod, _ = _discovered_registry() + return any(_provider_call(p, "is_available", False) for p in registry_mod.list_providers()) def _resolve_active_provider(): diff --git a/tools/vision_tools.py b/tools/vision_tools.py index f15404af4a..4de8eda492 100644 --- a/tools/vision_tools.py +++ b/tools/vision_tools.py @@ -806,14 +806,13 @@ def check_vision_requirements() -> bool: See #31179. """ - try: - from agent.auxiliary_client import aux_probe_mode, resolve_vision_provider_client - with aux_probe_mode(): - return any( - resolve_vision_provider_client(**kw)[1] is not None for kw in ({}, {"provider": "auto"}) - ) - except Exception: - return False + from agent.auxiliary_client import aux_probe_mode, resolve_vision_provider_client + # No blanket except: a resolver crash must reach the registry, which logs it with a + # traceback; a swallowed exception reads as "no vision backend configured" (#87950). + with aux_probe_mode(): + return any( + resolve_vision_provider_client(**kw)[1] is not None for kw in ({}, {"provider": "auto"}) + ) from tools.registry import registry, tool_error diff --git a/tools/x_search_tool.py b/tools/x_search_tool.py index abc574eeb9..d57f819350 100644 --- a/tools/x_search_tool.py +++ b/tools/x_search_tool.py @@ -78,10 +78,7 @@ def _resolve_xai_bearer() -> Tuple[str, str, str]: def check_x_search_requirements() -> bool: """True when xAI credentials resolve to a non-empty bearer (OAuth auto-refreshed).""" - try: - return bool(str(resolve_xai_http_credentials().get("api_key") or "").strip()) - except Exception: - return False + return bool(str(resolve_xai_http_credentials().get("api_key") or "").strip()) def _normalize_handles(handles: Optional[List[str]], field_name: str) -> List[str]: