diff --git a/backend/tests/test_doctor.py b/backend/tests/test_doctor.py index 97c0ba97d..9c8800cce 100644 --- a/backend/tests/test_doctor.py +++ b/backend/tests/test_doctor.py @@ -275,6 +275,24 @@ class TestCheckWebSearch: assert result.status == "ok" assert "DuckDuckGo" in result.detail + def test_commented_out_tools_block_warns_without_traceback(self, tmp_path): + # config.example.yaml ships a `tools:` key whose entries can all be + # commented out, so it parses as None rather than an empty list. + cfg = tmp_path / "config.yaml" + cfg.write_text("config_version: 5\ntools:\n # - name: web_search\n") + result = doctor.check_web_search(cfg) + assert result.status == "warn" + assert result.detail == "no web_search tool in config" + + def test_scalar_tools_entry_warns_without_traceback(self, tmp_path): + # A bare string entry is not a mapping; `t.get("name")` used to raise + # AttributeError, which the broad handler rendered as the check result. + cfg = tmp_path / "config.yaml" + cfg.write_text("config_version: 5\ntools:\n - web_search\n") + result = doctor.check_web_search(cfg) + assert result.status == "warn" + assert result.detail == "no web_search tool in config" + def test_tavily_with_key_ok(self, tmp_path, monkeypatch): monkeypatch.setenv("TAVILY_API_KEY", "tvly-test") cfg = tmp_path / "config.yaml" @@ -659,6 +677,17 @@ class TestCheckSandbox: results = doctor.check_sandbox(cfg) assert results[0].status == "fail" + def test_commented_out_tools_block_reports_no_traceback(self, tmp_path): + # Regression: iterating a null `tools:` raised TypeError, which the + # broad handler rendered as "('NoneType' object is not iterable)". + cfg = tmp_path / "config.yaml" + cfg.write_text("config_version: 5\nsandbox:\n use: deerflow.sandbox.local:LocalSandboxProvider\ntools:\n # - name: bash\n") + results = doctor.check_sandbox(cfg) + # Empty `tools:` means no bash tool, so the path is deterministic. + assert len(results) == 1 + assert results[0].status == "ok" + assert results[0].detail == "Local sandbox" + def test_local_sandbox_with_disabled_host_bash_warns(self, tmp_path): cfg = tmp_path / "config.yaml" cfg.write_text("config_version: 5\nsandbox:\n use: deerflow.sandbox.local:LocalSandboxProvider\n allow_host_bash: false\ntools:\n - name: bash\n use: deerflow.sandbox.tools:bash_tool\n") diff --git a/scripts/doctor.py b/scripts/doctor.py index b2830fd3b..ef998ebd1 100644 --- a/scripts/doctor.py +++ b/scripts/doctor.py @@ -473,7 +473,7 @@ def check_web_tool(config_path: Path, *, tool_name: str, label: str) -> CheckRes data = _load_yaml_file(config_path) - tool_entries = [t for t in data.get("tools", []) if t.get("name") == tool_name] + tool_entries = [t for t in (data.get("tools") or []) if isinstance(t, dict) and t.get("name") == tool_name] if not tool_entries: return CheckResult( label, @@ -642,7 +642,7 @@ def check_sandbox(config_path: Path) -> list[CheckResult]: ] sandbox_use = sandbox.get("use", "") - tools = data.get("tools", []) + tools = data.get("tools") or [] tool_names = {tool.get("name") for tool in tools if isinstance(tool, dict)} results: list[CheckResult] = []