Bots ingesting their own outbound messages as incoming
NousResearch/hermes-agent · cluster 11905 · gateway
Data as of
Nothing open on GitHub. Fix merged in #34838. Check it covers your case.
The short answer
- Known?
- Yes. 12 related items grouped here, 4 issues already closed.
- Fix event
- A fix was merged to main in PR #34838. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PRs #34838, #53571. That is separate from the fix event. - Still open
- Nothing open on GitHub.
Multiple integrations (Telegram, ntfy, Signal) share a common pattern where the bot or agent processes its own outbound messages as new inbound messages, causing echo loops or inflated unread counts. Members include bug reports and proposed fixes across these platforms.
Open issues0
Open PRs0
Fixes on record
-
#34838 fix(ntfy): prevent echo loop by tagging outgoing messages
files:
plugins/platforms/ntfy/adapter.py,scripts/release.py,tests/gateway/test_ntfy_plugin.py -
#53571 fix(telegram): ignore bot-authored self-messages to stop self-ingestion (#11905)
files:
plugins/platforms/telegram/adapter.py,tests/gateway/test_telegram_group_gating.pyShow the diff
-
plugins/platforms/telegram/adapter.py@@ -5863,6 +5863,8 @@ def _clean_bot_trigger_text(self, text: Optional[str]) -> Optional[str]: def _should_observe_unmentioned_group_message(self, message: Message) -> bool: """Return True when a group message should be stored but not dispatched.""" + if self._is_own_message(message): + return False if not self._telegram_observe_unmentioned_group_messages(): return False if not self._is_group_chat(message): @@ -6132,6 +6134,23 @@ def _observe_unmentioned_group_message( adapter_name = getattr(self, "name", "telegram") logger.warning("[%s] Failed to observe Telegram group message: %s", adapter_name, exc) + def _is_own_message(self, message: Message) -> bool: + """Return True when the message was sent by this bot itself. + + In some Telegram environments (groups, supergroups where the bot can + see its own messages), getUpdates returns the bot's own outgoing + messages as updates. These must be filtered out so they are not + counted as incoming unread messages in the Hermes inbox. + """ + if not self._bot: + return False + from_user = getattr(message, "from_user", None) + if from_user is None: + return False + bot_id = getattr(self._bot, "id", None) + user_id = getattr(from_user, "id", None) + return bot_id is not None and user_id is not None and bot_id == user_id + def _should_process_message(self, message: Message, *, is_command: bool = False) -> bool: """Apply Telegram group trigger rules. @@ -6154,6 +6173,13 @@ def _should_process_message(self, message: Message, *, is_command: bool = False) mentioning the bot (``@botname /command``), both of which are recognised as mentions by :meth:`_message_mentions_bot`. """ + # Filter out the bot's own messages (returned by getUpdates in some + # environments like groups/supergroups where the bot can see its own + # messages). Without this, outbound messages are counted as incoming + # unread in the Hermes inbox (#52363). + if self._is_own_message(message): + return False + if not self._is_group_chat(message): return True -
tests/gateway/test_telegram_group_gating.py@@ -636,6 +636,66 @@ def test_invalid_regex_patterns_are_ignored(): assert adapter._should_process_message(_group_message("hello everyone")) is False +def test_bot_self_messages_are_ignored_in_dm_and_group(): + """Bot-authored messages must not re-enter as fresh user turns (issue #11905). + + Telegram echoes the bot's own outbound messages back through getUpdates. + Without a self-author guard, those echoes — including + ``[SYSTEM: Background process ...]`` watcher notifications — get ingested + as new inbound turns, producing the "haunted topic" loop. The guard keys + on ``from_user.id == self._bot.id`` (bot id is 999 in ``_make_adapter``). + """ + adapter = _make_adapter(require_mention=False) + + # Control: a real user in the same group IS processed. + assert adapter._should_process_message(_group_message("hi", chat_id=-100)) is True + + # The exact reported symptom: a bot-authored DM-topic watcher echo. + self_dm = _group_message( + "[SYSTEM: Background process matched watch pattern ...]", + chat_id=555, + from_user_id=999, + ) + self_dm.chat.type = "private" + assert adapter._should_process_message(self_dm) is False + + # Same guard applies in groups/supergroups. + self_group = _group_message("status tick", chat_id=-100, from_user_id=999) + assert adapter._should_process_message(self_group) is False + + +def test_other_bots_are_still_processed(): + """A different bot's message must not be over-filtered. + + Distinguishes the self-id guard from a blanket ``from_user.is_bot`` check, + which would incorrectly drop unrelated bots (weather, music, etc.) sharing + the same chat. + """ + adapter = _make_adapter(require_mention=False) + other_bot = _group_message("weather update", chat_id=-100, from_user_id=555) + other_bot.from_user = SimpleNamespace(id=555, is_bot=True) + assert adapter._should_process_message(other_bot) is True + + +def test_self_message_guard_skips_observe_path(): + """Bot-authored messages are not stored via the observe-unmentioned path. + + When ``_should_process_message`` rejects a message, dispatch falls through + to ``_should_observe_unmentioned_group_message``; the self-guard must also + sit there so a self-echo is neither dispatched nor stored. + """ + adapter = _make_adapter(require_mention=True, observe_unmentioned_group_messages=True) + self_group = _group_message("status tick", chat_id=-100, from_user_id=999) + assert adapter._should_observe_unmentioned_group_message(self_group) is False + + +def test_missing_from_user_does_not_crash(): + adapter = _make_adapter(require_mention=False) + anon = _group_message("channel post", chat_id=-100) + anon.from_user = None + assert adapter._should_process_message(anon) is True + + def test_config_bridges_telegram_group_settings(monkeypatch, tmp_path): hermes_home = tmp_path / ".hermes" hermes_home.mkdir()
-
6 PRs closed without merging
From the close comments: done elsewhere 2 · merged via another change 2 · closed without comment 1 · superseded by another PR 1
- #11949 [codex] fix Telegram self-message ingestion in DMs · closed without comment
-
#11950 fix(telegram): ignore bot-authored messages to prevent self-ingestion · done elsewhere →
6fb25f8,#53571 -
#12817 fix(telegram): ignore messages authored by the bot itself · done elsewhere →
6fb25f8,#53571 -
#34453 fix(ntfy): prevent echo loop by deduplicating outgoing message IDs (#34446) · superseded by another PR →
#34838 -
#34486 fix(ntfy): prevent echo loop by tagging outgoing messages · merged via another change →
9405cdc,#34838
Show 1 more
-
#52667 fix(telegram): filter out bot's own messages from inbound processing · merged via another change →
6fb25f8,#53571,#11905
Closed issues · 4
- Closed#11905[Bug]: Telegram gateway processes its own bot-authored messages as inbound DM/topic turns
- Closed#34446Bug: ntfy echo loop — agent replies treated as incoming messages causing infinite spiral
- Closed#34447Bug: ntfy echo loop — agent replies treated as incoming messages causing infinite spiral
- Closed#52363Telegram: outbound messages counted as incoming unread (regression 0.16 → 0.17)
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.18: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab