Skip to content

[Bug]: Fix media routing to "All Chats" in Telegram DM Topics (Reply Fallback) #35739

@kmukul123

Description

@kmukul123

Bug Description

When a user interacts with Hermes inside a Telegram DM Topic (which uses Hermes's telegram_dm_topic_reply_fallback feature to simulate topics via reply anchors), text messages are routed correctly to the topic, but media messages (such as screenshots, images, and documents) are erroneously dropped into the root DM chat (visible as "All Chats" only) instead of staying within the topic thread.

Root Cause The Telegram Bot API does not support the message_thread_id parameter inside Private Chats (DMs) and will throw a BadRequest: message thread not found error if it is provided.

In gateway/platforms/telegram.py, the send method for standard text messages contained an explicit safeguard (if used_thread_fallback...) that stripped message_thread_id before sending, relying solely on reply_to_message_id to route the text message to the correct simulated topic.

However, media sending methods (such as send_multiple_images, send_image_file, send_document) did not have this safeguard. As a result:

Media methods attempted to send with a message_thread_id.
The Telegram Bot API rejected the request with a BadRequest.
Hermes's internal retry mechanism (_send_with_dm_topic_reply_anchor_retry) caught the exception and attempted to recover by stripping both message_thread_id and the reply_to_message_id anchor.
The media was then successfully resent, but without its reply anchor, causing it to drop out of the topic chain and land in the root chat.
Resolution Centralized the thread ID omission logic directly inside the _thread_kwargs_for_send helper method so that it applies globally across all message types.

Steps to Reproduce

To test this and verify the bug has been fixed, follow these steps:

Open your Telegram Direct Message (Private Chat) with Hermes.
Open any specific topic/thread (using a client that supports DM topics like Nagram, or via Hermes's reply-chain threads).
Send a command inside that specific topic that generates media, such as asking Hermes to take a /screenshot or generate an image.
Wait for Hermes to respond with the media.

Expected Behavior

Before the fix (The Bug): Hermes sends the screenshot, but because of the internal error, it loses its topic mapping. You receive it, but it shows up disconnected in your main "All Chats" view rather than inside the topic where you asked for it.
After the fix (Expected): Hermes sends the screenshot and it appears neatly as a direct reply inside the specific topic where you requested it, keeping your conversation history clean and organized.

Actual Behavior

Hermes sends the screenshot, but because of the internal error, it loses its topic mapping. You receive it, but it shows up disconnected in your main "All Chats" view rather than inside the topic where you asked for it.

Affected Component

Gateway (Telegram/Discord/Slack/WhatsApp)

Messaging Platform (if gateway-related)

Telegram

Debug Report

⚠️  This will upload the following to a public paste service:
  • System info (OS, Python version, Hermes version, provider, which API keys
    are configured — NOT the actual keys)
  • Recent log lines (agent.log, errors.log, gateway.log — may contain
    conversation fragments and file paths)
  • Full agent.log and gateway.log (up to 512 KB each — likely contains
    conversation content, tool outputs, and file paths)

Pastes auto-delete after 6 hours.

Collecting debug report...
Uploading...

Debug report uploaded:
  Report       https://paste.rs/WCmPP
  agent.log    https://paste.rs/2xqpD
  gateway.log  https://paste.rs/lT93d

⏱  Pastes will auto-delete in 6 hours.
To delete now:  hermes debug delete <url>

Operating System

Windows

Python Version

3.12

Hermes Version

0.15

Additional Logs / Traceback (optional)

Root Cause Analysis (optional)

No response

Proposed Fix (optional)

Changes Made File: gateway/platforms/telegram.py Method: _thread_kwargs_for_send

Modified the return statement for the telegram_dm_topic_reply_fallback block to return {"message_thread_id": None} instead of attempting to parse the thread_id.

diff

         if reply_to_message_id is None:
             direct_topic_id = cls._metadata_direct_messages_topic_id(metadata)
             if direct_topic_id is not None:
                 return {
                     "message_thread_id": None,
                     "direct_messages_topic_id": int(direct_topic_id),
                 }
             return {}
  •        return {"message_thread_id": cls._message_thread_id_for_send(thread_id)}
    
  •        return {"message_thread_id": None}
       direct_topic_id = cls._metadata_direct_messages_topic_id(metadata)
    

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/gatewayGateway runner, session dispatch, deliveryplatform/telegramTelegram bot adaptertype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions