fix(web): don't crash on a null web/backend config value

`_load_web_config()` is typed `-> dict` but returned `load_config().get("web",
{})`, which is `None` when the config has a present-but-null `web:` section
(YAML `web:` with no body). Every caller then does
`_load_web_config().get(...)` and raises `AttributeError: 'NoneType' object
has no attribute 'get'` — this hits `_get_backend`, `check_web_api_key`, and
the extract-char-limit reader.

Separately, `check_web_api_key()` read the backend as
`.get("backend", "").lower()`; a null `web.backend` value yields `None` (the
`""` default only applies when the key is absent), so `None.lower()` raised.
`check_web_api_key` is the `check_fn` gate for `web_search`/`web_extract`, so
this surfaced as an exception during tool-availability checking.

- Make `_load_web_config()` honor its `-> dict` contract (`... or {}`), fixing
  the null-`web:`-section crash at every call site.
- Guard the backend value in `check_web_api_key` with `or ""`, mirroring the
  existing guard in `_get_backend`.

Adds regression tests for both the null-backend-value and null-web-section
cases.
This commit is contained in:
HumphreySun98
2026-07-08 16:41:52 -05:00
committed by Teknium
parent 1a47769715
commit 5693265775
2 changed files with 24 additions and 2 deletions
+8 -2
View File
@@ -132,7 +132,11 @@ def _load_web_config() -> dict:
"""Load the ``web:`` section from ~/.hermes/config.yaml."""
try:
from hermes_cli.config import load_config
return load_config().get("web", {})
# ``or {}``: a present-but-null ``web:`` section (YAML ``web:`` with no
# body) makes ``.get("web", {})`` return None, which would break every
# caller that does ``_load_web_config().get(...)``. Honor the ``-> dict``
# contract so callers never see None.
return load_config().get("web") or {}
except (ImportError, Exception):
return {}
@@ -1004,7 +1008,9 @@ def check_web_api_key() -> bool:
:func:`_is_backend_available`, which delegates non-legacy names to the
registry.
"""
configured = _load_web_config().get("backend", "").lower().strip()
# ``or ""``: a null ``web.backend`` value yields None from ``.get``, and
# ``None.lower()`` would raise. Mirrors ``_get_backend``.
configured = (_load_web_config().get("backend") or "").lower().strip()
if configured and _is_backend_available(configured):
return True
# Any built-in backend with credentials present. This is a boolean OR, so