Skip to content

fix(telegram): handle channel senders and fix guest reply ordering - #49116

Closed
elphamale wants to merge 6 commits into
NousResearch:mainfrom
elphamale:fix/telegram-channel-sender-identity
Closed

fix(telegram): handle channel senders and fix guest reply ordering#49116
elphamale wants to merge 6 commits into
NousResearch:mainfrom
elphamale:fix/telegram-channel-sender-identity

Conversation

@elphamale

Copy link
Copy Markdown

Summary

Follow-up to #43049 — two fixes for group chat message handling discovered during testing of the guest mode feature:

  • Channel sender identity: when a user posts "as a channel" in a group, from_user is None and sender_chat carries the channel's id/title/username. Previously this set user_id=None, silently failing the auth check with no error surfaced to the user. Now sender_chat.id is used as the user_id fallback, so allow-lists can authorize the channel and the session key is stable across messages from the same channel.

  • Guest reply ordering: the Bot API 10.0 guest buffer check in send() was placed after the sendRichMessage fast-path (added in feat(telegram): implement Bot API 10.0 guest mode (@mention from non-member chats) #43049 for the legacy MarkdownV2 path). This caused send() to attempt sendMessage — which Telegram rejects with Forbidden: bot is not a member for non-member bots — before falling back to answerGuestQuery. Moving the guest check to the top of send() short-circuits before any network call is made, eliminating the spurious delivery-failure notification.

Test plan

  • Personal user @mention in group the bot is not a member of → bot responds via answerGuestQuery
  • Channel @mention (posting "as a channel") in the same group → sender_chat.id is used as user_id, auth passes if channel ID is in the allowlist, bot responds
  • No "Message delivery failed" error on guest replies when rich_messages is enabled
  • DM and member-group messages unaffected

🤖 Generated with Claude Code

elphamale and others added 6 commits June 19, 2026 16:19
…member chats)

PTB 22.6 doesn't natively support the `guest_message` update type or
`answerGuestQuery` method introduced in Telegram Bot API 10.0.  This
patch adds full guest bot support as a backward-compatible layer on top
of the existing adapter.

Changes:
- Register a `TypeHandler(Update, ...)` in group=1 to intercept
  `guest_message` updates, which PTB passes through via
  `update.api_kwargs` since they're unknown to its typed layer.
- Add `"guest_message"` to `allowed_updates` in all three start-polling
  / webhook paths so Telegram actually delivers the updates.
- Parse the raw `guest_message` payload via `Message.de_json()` and
  route it through the existing text-processing pipeline.
- Store the `guest_query_id` per chat in `_pending_guest_queries` and
  mark the chat in `_guest_only_chats` for the duration of the request.
- In `send()`, `send_draft()`, and `send_or_update_status()`, silently
  buffer/suppress all outgoing content for guest chats rather than
  attempting `sendMessage` (which would fail — the bot is not a member).
  `send()` keeps only the most recent write (final answer overwrites
  any earlier thinking/tool-progress text).
- In `on_processing_complete()`, flush the buffered reply via
  `answerGuestQuery` using an `InlineQueryResultArticle`-shaped payload,
  then clean up all per-chat guest state.  Flushing at completion means
  the user receives the full, coherent final answer rather than a
  mid-processing fragment.  The Telegram API imposes no documented
  timeout on `guest_query_id` (unlike `answerInlineQuery`), so deferral
  until completion is safe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… tests

Fix a stale-state bug: when _should_process_message() rejects a guest
message, _pending_guest_queries was popped but _guest_only_chats was
not discarded.  Any subsequent send() to that chat (e.g. after the bot
joins the group later) would be silently suppressed forever.

Add 12 unit tests covering the four scenarios raised in PR review:
- guest_query_id extraction and state setup in _handle_guest_message_update
- send() buffering instead of sendMessage for guest chats
- on_processing_complete answerGuestQuery flush and full state cleanup
- denial path: both _pending_guest_queries and _guest_only_chats cleared

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In P2P private chats, Telegram cannot post a bot message into the
conversation, so it surfaces the answerGuestQuery reply in the bot's DM
thread with the mentioning user instead of the original chat.  This is
Telegram API behaviour; our call is identical for groups and private
chats.  Add a code comment so future maintainers don't mistake the
private-chat routing for a bug in this implementation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
send_voice / send_video / send_image_file / send_document: add the same
guest-chat guard already present on send() / send_draft() /
send_or_update_status(). Without it, the adapter attempts bot.send_voice()
on a chat the bot is not a member of, getting a Telegram API rejection.

Also fix edit_message: return early on the "__no_edit__" stream_consumer
sentinel instead of crashing int("__no_edit__").

Add _resolve_workspace_path() to translate /workspace/<file> Docker container
paths to their host equivalents before os.path.exists() is called.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously send_audio/send_image/send_document/send_video returned
success=True (silent no-op) when the chat was a guest chat. The agent
saw "success", reported "sent silently", and the file never arrived in
the group. When the user complained the agent would explain the
limitation, but the initial false-success response was confusing.

Now these methods return success=False with an explicit error message
instructing the agent to send to the user's private DMs and notify
them in the group chat. The agent can then proactively communicate
the guest-mode media limitation before the user has to ask.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two related fixes for group chat message handling:

1. sender_chat identity: when a user posts "as a channel" in a group,
   from_user is None and sender_chat carries the channel's id/title/username.
   Previously this set user_id=None, silently failing the auth check.
   Now we fall back to sender_chat.id so allow-lists can authorize the
   channel and the session key is stable across messages.

2. Guest reply ordering: the Bot API 10.0 guest buffer check was placed
   after the rich-message fast-path, causing send() to attempt sendMessage
   (Forbidden for non-member bots) before falling back to answerGuestQuery.
   Move the guest check to the top of send() so it short-circuits before
   any network call is made.

Follows up on NousResearch#43049 (@elphamale).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@daimon-nous daimon-nous Bot added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter P2 Medium — degraded but workaround exists labels Jun 19, 2026
elphamale pushed a commit to elphamale/hermes-agent that referenced this pull request Jun 20, 2026
Upstream commit 5600105 moved gateway/platforms/telegram.py →
plugins/platforms/telegram/adapter.py.  This applies the equivalent
of PRs NousResearch#43049 / NousResearch#49116 / NousResearch#49186 to the new path:

- NousResearch#43049 (guest mode): _pending_guest_queries / _guest_only_chats /
  _guest_reply_buffer state; send() buffer block; TypeHandler
  registration; _handle_guest_message_update(); on_processing_complete()
  answerGuestQuery flush; media-method guards for send_voice /
  send_image_file / send_document / send_video / send_image.

- NousResearch#49116 (sender_chat): _build_message_event() uses sender_chat.id /
  .title when from_user is None (channel-as-user posts in groups).

- NousResearch#49186 (buffer quality): tool-progress drops (expect_edits/notify
  flags), cursor-strip before buffering, startswith-accumulation so
  cumulative streaming frames replace rather than double-append.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@elphamale

Copy link
Copy Markdown
Author

Closing as superseded by #56476.

Both fixes verified present in the rebuild: (1) the guest-buffer check is ordered before the rich/legacy send paths in send(), matching this PR's reply-ordering fix; (2) sender_chat handling for channel-posted messages (falls back to sender_chat.id/title/username when from_user is None) is in adapter.py.

@elphamale elphamale closed this Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/telegram Telegram bot adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant