From 1d74fea5e2edac3b826c8082ff1fbdeef0f4f227 Mon Sep 17 00:00:00 2001 From: 0xsir0000 <59465365+0xsir0000@users.noreply.github.com> Date: Wed, 13 May 2026 08:15:24 +0800 Subject: [PATCH] fix(gateway): honor gateway_restart_notification under top-level platform sections (#24644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PlatformConfig.gateway_restart_notification has been a no-op when set under the user-friendly top-level platform section (e.g. ``telegram:``) in config.yaml. The bridge loop in ``load_gateway_config`` (gateway/config.py:758) only forwarded a fixed allowlist of keys, and gateway_restart_notification wasn't on the list, so: 1. ``telegram.gateway_restart_notification: false`` never reached the platforms data dict. 2. ``PlatformConfig.from_dict()`` read ``False`` of the safe default (``True``) every time. 3. The suppression check at gateway/run.py:2767 always evaluated to truthy → shutdown/restart notifications were always sent. The issue's suggested fix (route the key through ``bridged``) is not quite right: ``extra.update(bridged)`` would land it in ``extra`` where ``PlatformConfig.from_dict()`` never looks. ``enabled`` already solves the same problem by writing directly to ``plat_data`` at the top level — mirror that path for gateway_restart_notification, and include it in the "should we materialise this platform at all" guard so the toggle works even when no other bridgeable keys are present. Tests cover false / explicit-true / unset (default-true) under top-level ``telegram:`` and ``discord:`` sections so future refactoring of the bridge loop trips a clear regression instead of silently re-breaking the toggle. Fixes #24644 --- gateway/config.py | 14 ++++++++- tests/gateway/test_config.py | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) 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