fix(gateway): tolerate scalar gateway config block

The streaming fallback path read yaml_cfg.get("gateway", {}).get("streaming") when top-level streaming was absent or malformed. If a user accidentally set gateway to a scalar value, config loading crashed with AttributeError instead of ignoring the malformed block and using defaults.

Read the gateway block once, verify it is a mapping before accessing nested streaming, and keep the existing gateway.platforms fallback using the same checked value.

Adds a regression test for config.yaml containing gateway: disabled.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sharziki 2026-06-06 18:45:43 -04:00 committed by Teknium
parent 50c66b2f8e
commit a7f65e3bcd
2 changed files with 19 additions and 2 deletions

View file

@ -1039,6 +1039,8 @@ def load_gateway_config() -> GatewayConfig:
if "stt_echo_transcripts" in yaml_cfg:
gw_data["stt_echo_transcripts"] = yaml_cfg["stt_echo_transcripts"]
gateway_cfg = yaml_cfg.get("gateway")
if "group_sessions_per_user" in yaml_cfg:
gw_data["group_sessions_per_user"] = yaml_cfg["group_sessions_per_user"]
@ -1066,7 +1068,11 @@ def load_gateway_config() -> GatewayConfig:
if not isinstance(streaming_cfg, dict):
# Fall back to nested gateway.streaming written by
# ``hermes config set gateway.streaming.*``
streaming_cfg = yaml_cfg.get("gateway", {}).get("streaming")
streaming_cfg = (
gateway_cfg.get("streaming")
if isinstance(gateway_cfg, dict)
else None
)
if isinstance(streaming_cfg, dict):
gw_data["streaming"] = streaming_cfg
@ -1099,7 +1105,6 @@ def load_gateway_config() -> GatewayConfig:
# ``gateway.platforms`` are loaded the same way as top-level
# ``platforms``. Merge nested first so top-level config keeps
# precedence, matching the existing gateway.streaming fallback.
gateway_cfg = yaml_cfg.get("gateway")
gateway_platforms = gateway_cfg.get("platforms") if isinstance(gateway_cfg, dict) else None
platforms_data = gw_data.setdefault("platforms", {})
if not isinstance(platforms_data, dict):

View file

@ -608,6 +608,18 @@ class TestLoadGatewayConfig:
assert config.max_concurrent_sessions == 2
def test_scalar_gateway_section_does_not_crash_streaming_fallback(self, tmp_path, monkeypatch):
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
config_path = hermes_home / "config.yaml"
config_path.write_text("gateway: disabled\n", encoding="utf-8")
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
config = load_gateway_config()
assert config.streaming.transport == "auto"
def test_bridges_discord_thread_require_mention_from_config_yaml(self, tmp_path, monkeypatch):
"""discord.thread_require_mention in config.yaml should reach the runtime env var."""
hermes_home = tmp_path / ".hermes"