Skip to content

[Bug] Webhook adapter drops telegram_reply_to_message_id from deliver_extra; all DM-topic webhook deliveries fail #33375

@konpyl

Description

@konpyl

Bug

When a webhook subscription has deliver: telegram with a message_thread_id in deliver_extra targeting a private DM with topics enabled, every delivery fails with:

Telegram DM topic delivery requires a reply anchor; refusing to send outside the requested topic — trying plain-text fallback
[Webhook] Fallback send also failed: Telegram DM topic delivery requires a reply anchor; refusing to send outside the requested topic

Hermes accepts the webhook (HTTP 202, delivered: true) but the Hermes → Telegram leg silently never sends. Reliably reproducible.

Repro

  1. Create a webhook subscription targeting a private DM topic:
    {
      "huddle": {
        "secret": "<route-secret>",
        "deliver": "telegram",
        "deliver_extra": {
          "chat_id": "<your-user-id>",
          "message_thread_id": "<topic-id-from-web.telegram.org-url>"
        },
        "prompt": "...",
        "skills": [...]
      }
    }
  2. Configure a Telegram bot in Hermes (TELEGRAM_BOT_TOKEN set).
  3. Fire any webhook test that posts to the route (e.g. signed curl -X POST http://<ip>:8644/webhooks/huddle).
  4. Hermes accepts ({"delivered": true, "status": 202}).
  5. Nothing arrives in Telegram. Logs show the "reply anchor" error.

Root Cause

gateway/platforms/webhook.py:918-924 builds metadata for the cross-platform Telegram dispatch as:

metadata = None
thread_id = extra.get("message_thread_id") or extra.get("thread_id")
if thread_id:
    metadata = {"thread_id": thread_id}

This is passed to TelegramAdapter.send(), which calls _is_private_dm_topic_send() at gateway/platforms/telegram.py:591. That function returns True for any private DM with a thread_id and no opt-out flag, which triggers the anchor demand at gateway/platforms/telegram.py:611.

The webhook adapter has no way to provide telegram_reply_to_message_id (the only field that satisfies the guard), and no way to set the telegram_dm_topic_created_for_send or direct_messages_topic_id opt-out flags from deliver_extra.

Empirically, the bare message_thread_id form actually works for these chats — directly hitting api.telegram.org/bot<token>/sendMessage with chat_id=<user>&message_thread_id=<topic> succeeds and lands in the right topic. The guard is over-strict for this code path.

Related

The webhook adapter wasn't covered by any of those.

Workaround

Post-start patch hook that sets telegram_dm_topic_created_for_send: True alongside thread_id, which bypasses the over-cautious adapter guard:

metadata = {
    "thread_id": thread_id,
    "telegram_dm_topic_created_for_send": True,
}

Applied via a bind-mounted script that runs before hermes init (so it survives image updates).

Suggested Fix

Two reasonable options:

  1. Surface the existing opt-out flags via deliver_extra — let webhook subscriptions explicitly say "I've validated this thread_id; bypass the anchor check" by passing one of the existing flags (telegram_dm_topic_created_for_send, direct_messages_topic_id, or telegram_reply_to_message_id) through to metadata. ~4 lines.

  2. Relax the adapter guard for the webhook code path — operator-configured webhook routes are by definition pre-validated; the anchor check makes sense for synthetic/recovered sends, not for explicit static config. Detect the webhook source and skip the guard, matching how telegram_dm_topic_created_for_send already works for in-session creates.

Environment

  • Hermes: nousresearch/hermes-agent:latest (digest fac5c1306df3..., pushed 2026-05-27 13:13 UTC)
  • Bot API context: private DM with topics enabled (Bot API 10+)
  • 100% reproducible on the setup above

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/gatewayGateway runner, session dispatch, deliveryplatform/telegramTelegram bot adapterplatform/webhookWebhook / API servertype/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