diff --git a/gateway/config.py b/gateway/config.py index 16e2662e81912..2b44cedde378c 100644 --- a/gateway/config.py +++ b/gateway/config.py @@ -808,7 +808,15 @@ def load_gateway_config() -> GatewayConfig: else: bridged["channel_prompts"] = channel_prompts enabled_was_explicit = "enabled" in platform_cfg - if not bridged and not enabled_was_explicit: + # gateway_restart_notification is a top-level field on + # PlatformConfig (not part of extra), so it has to land on + # plat_data directly the same way enabled does — pushing + # it through `bridged` would stash it in extra where + # PlatformConfig.from_dict() never looks. + restart_notify_was_explicit = ( + "gateway_restart_notification" in platform_cfg + ) + if not bridged and not enabled_was_explicit and not restart_notify_was_explicit: continue plat_data = platforms_data.setdefault(plat.value, {}) if not isinstance(plat_data, dict): @@ -816,6 +824,10 @@ def load_gateway_config() -> GatewayConfig: platforms_data[plat.value] = plat_data if enabled_was_explicit: plat_data["enabled"] = platform_cfg["enabled"] + if restart_notify_was_explicit: + plat_data["gateway_restart_notification"] = platform_cfg[ + "gateway_restart_notification" + ] extra = plat_data.setdefault("extra", {}) if not isinstance(extra, dict): extra = {} diff --git a/tests/gateway/test_config.py b/tests/gateway/test_config.py index c59b27d8001b4..b8a224d8099c2 100644 --- a/tests/gateway/test_config.py +++ b/tests/gateway/test_config.py @@ -542,6 +542,65 @@ def test_telegram_proxy_env_takes_precedence_over_config(self, tmp_path, monkeyp import os assert os.environ.get("TELEGRAM_PROXY") == "socks5://from-env:1080" + def test_bridges_gateway_restart_notification_from_telegram_section(self, tmp_path, monkeypatch): + # Regression for #24644: setting gateway_restart_notification under + # a top-level platform section (e.g. ``telegram:``) was silently + # ignored — the bridge loop only copied a fixed allowlist of keys + # and PlatformConfig.from_dict() reads gateway_restart_notification + # from the platform's top-level data, never from extra. + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + config_path = hermes_home / "config.yaml" + config_path.write_text( + "telegram:\n" + " gateway_restart_notification: false\n", + encoding="utf-8", + ) + + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + config = load_gateway_config() + + assert config.platforms[Platform.TELEGRAM].gateway_restart_notification is False + + def test_bridges_gateway_restart_notification_true_explicit(self, tmp_path, monkeypatch): + # Explicit ``true`` should also flow through (and match the + # default), proving the bridge isn't accidentally swallowing the + # key when its value already matches the default. + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + config_path = hermes_home / "config.yaml" + config_path.write_text( + "discord:\n" + " gateway_restart_notification: true\n", + encoding="utf-8", + ) + + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + config = load_gateway_config() + + assert config.platforms[Platform.DISCORD].gateway_restart_notification is True + + def test_gateway_restart_notification_defaults_true_when_unset(self, tmp_path, monkeypatch): + # Sanity check that omitting the key keeps the existing default + # so this fix doesn't accidentally change behavior for existing + # users who never set it. + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + config_path = hermes_home / "config.yaml" + config_path.write_text( + "telegram:\n" + " require_mention: true\n", + encoding="utf-8", + ) + + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + config = load_gateway_config() + + assert config.platforms[Platform.TELEGRAM].gateway_restart_notification is True + class TestHomeChannelEnvOverrides: """Home channel env vars should apply even when the platform was already