# NousResearch/hermes-agent · Telegram thread ID session-splitting bug fixes

## Open now

- Nothing open on GitHub.

These issues and pull requests address a bug where Telegram group messages carrying message_thread_id values cause unexpected session splits or misrouted human-handoff replies. Fixes center on normalizing thread IDs through a shared helper to prevent incorrect session routing.

## TL;DR

- 5 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #28506. Verify it addresses your case.
- **Recommended:** read PR #28506 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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.34`: the share of member 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.

## Closed / duplicate issues

- #13195: Telegram groups can split into unexpected sessions when inbound messages carry message_thread_id without user-created topics
- #28153: Telegram group reply-derived thread IDs can route human-handoff “done” replies into the wrong session

## Prior & competing PRs

- **#13338: fix(telegram): avoid unexpected thread session splits**. closed without merge · fix tier `suggested`
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `7b2bcba1679e2bf0c73612ce9583982d970162d3`, `#13195`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#28506: fix: avoid Telegram group reply thread session splits**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `gateway/platforms/telegram.py`, `scripts/release.py`, `tests/gateway/test_dm_topics.py`, `tests/gateway/test_telegram_thread_fallback.py`
  - fix hunks:
  - `gateway/platforms/telegram.py`

    ```diff
    @@ -4795,25 +4795,28 @@ def _build_message_event(
             elif chat.type == ChatType.CHANNEL:
                 chat_type = "channel"
     
    -        # Resolve DM topic name and skill binding.
    -        # In private chats, only preserve thread ids for real topic messages
    -        # (is_topic_message=True).  Telegram puts message_thread_id on every
    -        # DM that is a reply, even when the user is just replying to a
    -        # previous message in the same DM — that bogus id then routes to a
    -        # nonexistent thread and Telegram returns 'Message thread not found'
    -        # on send (#3206).
    +        # Resolve Telegram topic name and skill binding.
    +        # Only preserve message_thread_id when Telegram marks the message as
    +        # a real topic/forum message. Telegram can also populate
    +        # message_thread_id for ordinary reply UI anchors; treating those as
    +        # durable session threads fragments workflows such as CAPTCHA/login
    +        # handoffs where the user later replies "done" in the same group.
    +        # Private chats have the same pitfall: only real DM topic messages
    +        # (is_topic_message=True) should keep the thread id, otherwise sends
    +        # can hit Telegram's 'Message thread not found' error (#3206).
             thread_id_raw = message.message_thread_id
             is_topic_message = bool(getattr(message, "is_topic_message", False))
    +        is_forum_group = getattr(chat, "is_forum", False) is True
             thread_id_str = None
             if thread_id_raw is not None:
    -            if chat_type == "group":
    +            if chat_type == "group" and (is_topic_message or is_forum_group):
                     thread_id_str = str(thread_id_raw)
                 elif chat_type == "dm" and is_topic_message:
                     thread_id_str = str(thread_id_raw)
             # For forum groups without an explicit topic, default to the
             # General-topic id so the gateway routes back to the General topic
             # rather than dropping into the bot's main channel (#22423).
    -        if chat_type == "group" and thread_id_str is None and getattr(chat, "is_forum", False):
    +        if chat_type == "group" and thread_id_str is None and is_forum_group:
                 thread_id_str = self._GENERAL_TOPIC_THREAD_ID
             chat_topic = None
             topic_skill = None
    ```
  - `scripts/release.py`

    ```diff
    @@ -171,6 +171,7 @@
         "[redacted:email]": "fabiosiqueira",  # PR #27212 salvage (bg-process notif anchor)
         "[redacted:email]": "falconexe",  # PR #24511 salvage (sticky-IP reset)
         "[redacted:email]": "fonhal",  # PR #27865/#27861 salvage (mention entities / typing fallback)
    +    "[redacted:email]": "eliteworkstation94-ai",  # PR #28157 salvage (group reply session splits)
         "[redacted:email]": "hllqkb",
         "[redacted:email]": "Bongulielmi",
         "[redacted:email]": "JTroyerOvermatch",
    ```
  - `tests/gateway/test_dm_topics.py`

    ```diff
    @@ -449,13 +449,15 @@ def test_cache_dm_topic_from_message_no_overwrite():
     
     def _make_mock_message(chat_id=111, chat_type="private", text="hello", thread_id=None,
                            user_id=42, user_name="Test User", forum_topic_created=None,
    -                       is_topic_message=None):
    +                       is_topic_message=None, is_forum=None):
         """Create a mock Telegram Message for _build_message_event tests."""
         chat = SimpleNamespace(
             id=chat_id,
             type=chat_type,
             title=None,
         )
    +    if is_forum is not None:
    +        chat.is_forum = is_forum
         # Add full_name attribute for DM chats
         if not hasattr(chat, "full_name"):
             chat.full_name = user_name
    ```

    ```diff
    @@ -594,7 +596,12 @@ def test_group_topic_skill_binding():
         ])
     
         msg = _make_mock_message(
    -        chat_id=-1001234567890, chat_type=_ChatType.SUPERGROUP, thread_id=5, text="hello"
    +        chat_id=-1001234567890,
    +        chat_type=_ChatType.SUPERGROUP,
    +        thread_id=5,
    +        text="hello",
    +        is_topic_message=True,
    +        is_forum=True,
         )
         event = adapter._build_message_event(msg, MessageType.TEXT)
     
    ```

    ```diff
    @@ -617,7 +624,12 @@ def test_group_topic_skill_binding_second_topic():
         ])
     
         msg = _make_mock_message(
    -        chat_id=-1001234567890, chat_type=_ChatType.SUPERGROUP, thread_id=12, text="deal update"
    +        chat_id=-1001234567890,
    +        chat_type=_ChatType.SUPERGROUP,
    +        thread_id=12,
    +        text="deal update",
    +        is_topic_message=True,
    +        is_forum=True,
         )
         event = adapter._build_message_event(msg, MessageType.TEXT)
     
    ```

    ```diff
    @@ -639,7 +651,12 @@ def test_group_topic_no_skill_binding():
         ])
     
         msg = _make_mock_message(
    -        chat_id=-1001234567890, chat_type=_ChatType.SUPERGROUP, thread_id=1, text="hey"
    +        chat_id=-1001234567890,
    +        chat_type=_ChatType.SUPERGROUP,
    +        thread_id=1,
    +        text="hey",
    +        is_topic_message=True,
    +        is_forum=True,
         )
         event = adapter._build_message_event(msg, MessageType.TEXT)
     
    ```

    ```diff
    @@ -661,7 +678,12 @@ def test_group_topic_unmapped_thread_id():
         ])
     
         msg = _make_mock_message(
    -        chat_id=-1001234567890, chat_type=_ChatType.SUPERGROUP, thread_id=999, text="random"
    +        chat_id=-1001234567890,
    +        chat_type=_ChatType.SUPERGROUP,
    +        thread_id=999,
    +        text="random",
    +        is_topic_message=True,
    +        is_forum=True,
         )
         event = adapter._build_message_event(msg, MessageType.TEXT)
     
    ```

    ```diff
    @@ -683,7 +705,12 @@ def test_group_topic_unmapped_chat_id():
         ])
     
         msg = _make_mock_message(
    -        chat_id=-1009999999999, chat_type=_ChatType.SUPERGROUP, thread_id=5, text="wrong group"
    +        chat_id=-1009999999999,
    +        chat_type=_ChatType.SUPERGROUP,
    +        thread_id=5,
    +        text="wrong group",
    +        is_topic_message=True,
    +        is_forum=True,
         )
         event = adapter._build_message_event(msg, MessageType.TEXT)
     
    ```

    ```diff
    @@ -720,7 +747,12 @@ def test_group_topic_chat_id_int_string_coercion():
         ])
     
         msg = _make_mock_message(
    -        chat_id=-1001234567890, chat_type=_ChatType.SUPERGROUP, thread_id=7, text="test"
    +        chat_id=-1001234567890,
    +        chat_type=_ChatType.SUPERGROUP,
    +        thread_id=7,
    +        text="test",
    +        is_topic_message=True,
    +        is_forum=True,
         )
         event = adapter._build_message_event(msg, MessageType.TEXT)
     
    ```
  - `tests/gateway/test_telegram_thread_fallback.py`

    ```diff
    @@ -134,6 +134,70 @@ def _make_adapter():
         return adapter
     
     
    +def test_non_forum_group_reply_thread_id_does_not_fork_session_key():
    +    """Reply-derived thread ids in ordinary groups must not create topic lanes."""
    +    from gateway.platforms import telegram as telegram_mod
    +
    +    adapter = _make_adapter()
    +    message = SimpleNamespace(
    +        text="Done",
    +        caption=None,
    +        chat=SimpleNamespace(
    +            id=-100123,
    +            type=telegram_mod.ChatType.SUPERGROUP,
    +            is_forum=False,
    +            title="Regular group",
    +        ),
    +        from_user=SimpleNamespace(id=456, full_name="Alice"),
    +        message_thread_id=461,
    +        is_topic_message=False,
    +        reply_to_message=SimpleNamespace(
    +            message_id=460,
    +            text="Please complete the CAPTCHA/login, then reply done.",
    +            caption=None,
    +        ),
    +        message_id=462,
    +        date=None,
    +    )
    +
    +    event = adapter._build_message_event(message, msg_type=MessageType.TEXT)
    +
    +    assert event.source.chat_id == "-100123"
    +    assert event.source.chat_type == "group"
    +    assert event.source.thread_id is None
    +    assert build_session_key(event.source) == "agent:main:telegram:group:-100123:456"
    +
    +
    +def test_forum_group_topic_message_preserves_thread_session_key():
    +    """Real Telegram forum-topic messages should still route by topic id."""
    +    from gateway.platforms import telegram as telegram_mod
    +
    +    adapter = _make_adapter()
    +    message = SimpleNamespace(
    +        text="hello from topic",
    +        caption=None,
    +        chat=SimpleNamespace(
    +            id=-100123,
    +            type=telegram_mod.ChatType.SUPERGROUP,
    +            is_forum=True,
    +            title="Forum group",
    +        ),
    +        from_user=SimpleNamespace(id=456, full_name="Alice"),
    +        message_thread_id=17585,
    +        is_topic_message=True,
    +        reply_to_message=None,
    +        message_id=10,
    +        date=None,
    +    )
    +
    +    event = adapter._build_message_event(message, msg_type=MessageType.TEXT)
    +
    +    assert event.source.chat_id == "-100123"
    +    assert event.source.chat_type == "group"
    +    assert event.source.thread_id == "17585"
    +    assert build_session_key(event.source) == "agent:main:telegram:group:-100123:17585"
    +
    +
     def test_forum_general_topic_without_message_thread_id_keeps_thread_context():
         """Forum General-topic messages should keep synthetic thread context."""
         from gateway.platforms import telegram as telegram_mod
    ```
- **#28157: fix: avoid Telegram group reply thread session splits**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#28506`

## Fix status

- Merged to main in PR #28506 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-19`.
- Subsystem: `gateway`
