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

0open issues
0open PRs
12items grouped
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

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #34838 fix(ntfy): prevent echo loop by tagging outgoing messages

    merged to main

    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)

    merged to main

    files: plugins/platforms/telegram/adapter.py, tests/gateway/test_telegram_group_gating.py

    Show 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.00 means 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

At a glance

Subsystem
gateway
Merged PRs
#34838, #53571
Closed issues
4
Attempts
6 PRs closed without merging
Fix link checked
27 Jun 2026

For your agent

The same facts, typed.