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.
This commit is contained in:
teknium1
2026-09-13 12:20:17 -07:00
committed by Teknium
parent aa7980d777
commit 643b3f450d
7 changed files with 30 additions and 34 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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())
# ---------------------------------------------------------------------------

View File

@@ -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 ---

View File

@@ -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():

View File

@@ -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

View File

@@ -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]: