{"repo":"NousResearch/hermes-agent","public_id":"9208","display_label":"Telegram ignored threads config feature request","summary":"These items all concern adding an `ignored_threads` configuration option to the Telegram gateway for filtering specific topics or threads. The group includes a feature request and related implementation pull requests.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":9530,"merged_at":"2026-04-14T08:40:32+00:00"},"resolution":{"state":"resolved","by":"pr-9530"},"freshness":{"state":"stale","last_verified_at":"2026-04-14T08:40:32+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":9208,"pr_number":9530,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-14T08:40:32+00:00"}],"members":[{"kind":"issue","number":9208,"title":"[Feature]: ignored_threads config for Telegram topic filtering","url":null,"state":"closed"},{"kind":"pr","number":9348,"title":"feat(gateway): add ignored_threads config for Telegram","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#9530","number":9530}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":9530,"title":"feat(gateway): add ignored_threads config for Telegram","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["gateway/config.py","gateway/platforms/telegram.py","tests/gateway/test_telegram_group_gating.py"],"fix_hunks":[{"file":"gateway/config.py","hunks":["@@ -625,6 +625,11 @@ def load_gateway_config() -> GatewayConfig:\n                     if isinstance(frc, list):\n                         frc = \",\".join(str(v) for v in frc)\n                     os.environ[\"TELEGRAM_FREE_RESPONSE_CHATS\"] = str(frc)\n+                ignored_threads = telegram_cfg.get(\"ignored_threads\")\n+                if ignored_threads is not None and not os.getenv(\"TELEGRAM_IGNORED_THREADS\"):\n+                    if isinstance(ignored_threads, list):\n+                        ignored_threads = \",\".join(str(v) for v in ignored_threads)\n+                    os.environ[\"TELEGRAM_IGNORED_THREADS\"] = str(ignored_threads)\n                 if \"reactions\" in telegram_cfg and not os.getenv(\"TELEGRAM_REACTIONS\"):\n                     os.environ[\"TELEGRAM_REACTIONS\"] = str(telegram_cfg[\"reactions\"]).lower()\n "]},{"file":"gateway/platforms/telegram.py","hunks":["@@ -1991,6 +1991,27 @@ def _telegram_free_response_chats(self) -> set[str]:\n             return {str(part).strip() for part in raw if str(part).strip()}\n         return {part.strip() for part in str(raw).split(\",\") if part.strip()}\n \n+    def _telegram_ignored_threads(self) -> set[int]:\n+        raw = self.config.extra.get(\"ignored_threads\")\n+        if raw is None:\n+            raw = os.getenv(\"TELEGRAM_IGNORED_THREADS\", \"\")\n+\n+        if isinstance(raw, list):\n+            values = raw\n+        else:\n+            values = str(raw).split(\",\")\n+\n+        ignored: set[int] = set()\n+        for value in values:\n+            text = str(value).strip()\n+            if not text:\n+                continue\n+            try:\n+                ignored.add(int(text))\n+            except (TypeError, ValueError):\n+                logger.warning(\"[%s] Ignoring invalid Telegram thread id: %r\", self.name, value)\n+        return ignored\n+\n     def _compile_mention_patterns(self) -> List[re.Pattern]:\n         \"\"\"Compile optional regex wake-word patterns for group triggers.\"\"\"\n         patterns = self.config.extra.get(\"mention_patterns\")","@@ -2102,6 +2123,13 @@ def _should_process_message(self, message: Message, *, is_command: bool = False)\n         \"\"\"\n         if not self._is_group_chat(message):\n             return True\n+        thread_id = getattr(message, \"message_thread_id\", None)\n+        if thread_id is not None:\n+            try:\n+                if int(thread_id) in self._telegram_ignored_threads():\n+                    return False\n+            except (TypeError, ValueError):\n+                logger.warning(\"[%s] Ignoring non-numeric Telegram message_thread_id: %r\", self.name, thread_id)\n         if str(getattr(getattr(message, \"chat\", None), \"id\", \"\")) in self._telegram_free_response_chats():\n             return True\n         if not self._telegram_require_mention():"]},{"file":"tests/gateway/test_telegram_group_gating.py","hunks":["@@ -5,7 +5,7 @@\n from gateway.config import Platform, PlatformConfig, load_gateway_config\n \n \n-def _make_adapter(require_mention=None, free_response_chats=None, mention_patterns=None):\n+def _make_adapter(require_mention=None, free_response_chats=None, mention_patterns=None, ignored_threads=None):\n     from gateway.platforms.telegram import TelegramAdapter\n \n     extra = {}","@@ -15,6 +15,8 @@ def _make_adapter(require_mention=None, free_response_chats=None, mention_patter\n         extra[\"free_response_chats\"] = free_response_chats\n     if mention_patterns is not None:\n         extra[\"mention_patterns\"] = mention_patterns\n+    if ignored_threads is not None:\n+        extra[\"ignored_threads\"] = ignored_threads\n \n     adapter = object.__new__(TelegramAdapter)\n     adapter.platform = Platform.TELEGRAM","@@ -28,7 +30,16 @@ def _make_adapter(require_mention=None, free_response_chats=None, mention_patter\n     return adapter\n \n \n-def _group_message(text=\"hello\", *, chat_id=-100, reply_to_bot=False, entities=None, caption=None, caption_entities=None):\n+def _group_message(\n+    text=\"hello\",\n+    *,\n+    chat_id=-100,\n+    thread_id=None,\n+    reply_to_bot=False,\n+    entities=None,\n+    caption=None,\n+    caption_entities=None,\n+):\n     reply_to_message = None\n     if reply_to_bot:\n         reply_to_message = SimpleNamespace(from_user=SimpleNamespace(id=999))","@@ -37,6 +48,7 @@ def _group_message(text=\"hello\", *, chat_id=-100, reply_to_bot=False, entities=N\n         caption=caption,\n         entities=entities or [],\n         caption_entities=caption_entities or [],\n+        message_thread_id=thread_id,\n         chat=SimpleNamespace(id=chat_id, type=\"group\"),\n         reply_to_message=reply_to_message,\n     )","@@ -69,6 +81,14 @@ def test_free_response_chats_bypass_mention_requirement():\n     assert adapter._should_process_message(_group_message(\"hello everyone\", chat_id=-201)) is False\n \n \n+def test_ignored_threads_drop_group_messages_before_other_gates():\n+    adapter = _make_adapter(require_mention=False, free_response_chats=[\"-200\"], ignored_threads=[31, \"42\"])\n+\n+    assert adapter._should_process_message(_group_message(\"hello everyone\", chat_id=-200, thread_id=31)) is False\n+    assert adapter._should_process_message(_group_message(\"hello everyone\", chat_id=-200, thread_id=42)) is False\n+    assert adapter._should_process_message(_group_message(\"hello everyone\", chat_id=-200, thread_id=99)) is True\n+\n+\n def test_regex_mention_patterns_allow_custom_wake_words():\n     adapter = _make_adapter(require_mention=True, mention_patterns=[r\"^\\s*chompy\\b\"])\n ","@@ -108,3 +128,23 @@ def test_config_bridges_telegram_group_settings(monkeypatch, tmp_path):\n     assert __import__(\"os\").environ[\"TELEGRAM_REQUIRE_MENTION\"] == \"true\"\n     assert json.loads(__import__(\"os\").environ[\"TELEGRAM_MENTION_PATTERNS\"]) == [r\"^\\s*chompy\\b\"]\n     assert __import__(\"os\").environ[\"TELEGRAM_FREE_RESPONSE_CHATS\"] == \"-123\"\n+\n+\n+def test_config_bridges_telegram_ignored_threads(monkeypatch, tmp_path):\n+    hermes_home = tmp_path / \".hermes\"\n+    hermes_home.mkdir()\n+    (hermes_home / \"config.yaml\").write_text(\n+        \"telegram:\\n\"\n+        \"  ignored_threads:\\n\"\n+        \"    - 31\\n\"\n+        \"    - \\\"42\\\"\\n\",\n+        encoding=\"utf-8\",\n+    )\n+\n+    monkeypatch.setenv(\"HERMES_HOME\", str(hermes_home))\n+    monkeypatch.delenv(\"TELEGRAM_IGNORED_THREADS\", raising=False)\n+\n+    config = load_gateway_config()\n+\n+    assert config is not None\n+    assert __import__(\"os\").environ[\"TELEGRAM_IGNORED_THREADS\"] == \"31,42\"\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}