From 95520b812f4087ad201aab4dbed292494e8f421b Mon Sep 17 00:00:00 2001 From: bex <30572280+JoshPaulie@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:21:55 -0500 Subject: [PATCH] fix(agent): fail open on malformed telegram extra config Guard both extra lookups with isinstance(dict) before merging, so a truthy non-mapping `extra` value (e.g. `extra: "true"`) degrades to the base Telegram hint instead of raising TypeError and aborting system-prompt construction. Keep the narrowed except ImportError. Add an integration test exercising the real config path (HERMES_HOME + gateway.platforms.telegram.extra.rich_messages) and a regression test for the malformed-extra fail-open path. The integration test fails on main and passes with the fix. --- agent/system_prompt.py | 8 ++++-- tests/agent/test_system_prompt.py | 42 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/agent/system_prompt.py b/agent/system_prompt.py index e89760b8a6458..9b841683b2ce4 100644 --- a/agent/system_prompt.py +++ b/agent/system_prompt.py @@ -454,8 +454,12 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None) try: from hermes_cli.config import load_config_readonly _cfg = load_config_readonly() - _gw_tg_extra = (((_cfg.get("gateway") or {}).get("platforms") or {}).get("telegram") or {}).get("extra") or {} - _top_tg_extra = ((_cfg.get("platforms") or {}).get("telegram") or {}).get("extra") or {} + _gw_tg_extra = (((_cfg.get("gateway") or {}).get("platforms") or {}).get("telegram") or {}).get("extra") + _top_tg_extra = ((_cfg.get("platforms") or {}).get("telegram") or {}).get("extra") + if not isinstance(_gw_tg_extra, dict): + _gw_tg_extra = {} + if not isinstance(_top_tg_extra, dict): + _top_tg_extra = {} _tg_extra = {**_gw_tg_extra, **_top_tg_extra} if _tg_extra.get("rich_messages"): _default_hint = _default_hint.rstrip() + " " + TELEGRAM_RICH_MESSAGES_HINT diff --git a/tests/agent/test_system_prompt.py b/tests/agent/test_system_prompt.py index 11f1030e66368..eb1b9c048ad38 100644 --- a/tests/agent/test_system_prompt.py +++ b/tests/agent/test_system_prompt.py @@ -259,6 +259,48 @@ class TestTelegramRichMessagesHint: assert "lean into it" not in stable + def test_gateway_rich_messages_integration_via_real_config(self, tmp_path, monkeypatch): + """End-to-end through the real config-resolution chain: a config.yaml + under HERMES_HOME with ``gateway.platforms.telegram.extra.rich_messages`` + must activate the rich hint. ``load_config_readonly`` is NOT mocked here, + so this guards against the exact path-mismatch bug this PR fixes. + """ + config_yaml = ( + "gateway:\n" + " platforms:\n" + " telegram:\n" + " extra:\n" + " rich_messages: true\n" + ) + home = tmp_path / "hermes_home" + home.mkdir() + (home / "config.yaml").write_text(config_yaml) + + monkeypatch.setenv("HERMES_HOME", str(home)) + # Point config resolution at the temp file without mocking the loader: + # mirror the pattern used in test_config_env_expansion.py. + from hermes_cli import config as _cfgmod + monkeypatch.setattr(_cfgmod, "get_config_path", lambda: home / "config.yaml") + + agent = _make_agent(platform="telegram") + stable = _stable_prompt(agent) + assert "lean into it" in stable + assert "task lists" in stable + + def test_malformed_extra_value_falls_back_to_base_hint(self, tmp_path, monkeypatch): + """A truthy non-mapping ``extra`` must not crash prompt construction — + it should fail open to the base hint (Tek's fail-open concern). + """ + agent = _make_agent(platform="telegram") + with patch("hermes_cli.config.load_config_readonly") as mock_cfg: + mock_cfg.return_value = { + "gateway": {"platforms": {"telegram": {"extra": "not-a-map"}}} + } + stable = _stable_prompt(agent) + assert "Standard Markdown is automatically converted" in stable + assert "lean into it" not in stable + + _SKILLS = "SKILLS_INDEX_SENTINEL" _CONTEXT = "CONTEXT_FILES_SENTINEL"