fix(telegram): named DM topic delivery + private DM topic anchor contract (salvage of #27107)#32270
Merged
Conversation
(cherry picked from commit ad8f97d)
(cherry picked from commit 3d585f8)
(cherry picked from commit 6daafb3)
(cherry picked from commit 5cde061)
(cherry picked from commit 26b8705)
(cherry picked from commit bf8048a)
Salvage follow-up. The transient thread-not-found retry test was exercising chat_id='123' (positive, looks-like-private) which now hits the new private-DM-topic fail-closed contract. The test's intent is the transient-flake retry on real forum topics in groups, so use -100123 to make the scenario unambiguous.
…quired guard Salvage follow-up. The new private-DM-topic fail-loud contract from PR #27107 hits 'requires a reply anchor' when reply_to_mode='off' is configured, even though commit 21a15b6 (PR #23994) verified that message_thread_id alone routes correctly on python-telegram-bot's reference client when the user has explicitly opted out of quote bubbles. Carve out the explicit opt-in path so users on reply_to_mode 'off' aren't regressed — the new guard now only applies to callers that didn't ask for the anchor to be suppressed.
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
1 |
First entries
tests/gateway/test_delivery.py:3: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
✅ Fixed issues: none
Unchanged: 4946 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
19 tasks
briandevans
added a commit
to briandevans/hermes-agent
that referenced
this pull request
May 30, 2026
PR NousResearch#32270 (2026-05-25) introduced a fail-loud guard in `TelegramAdapter.send()`: a private-chat send carrying a thread_id is rejected with "requires a reply anchor" unless metadata includes one of `telegram_reply_to_message_id`, `direct_messages_topic_id`, or `telegram_dm_topic_created_for_send`. The agent/cron paths populate those keys themselves, but `WebhookAdapter._deliver_cross_platform` only forwarded `thread_id`, so any webhook subscription targeting a private DM topic now fails on every delivery — Hermes accepts the webhook (`HTTP 202`, `delivered: true`), then the cross-platform send is refused before it ever hits Telegram. Plumb the four DM-topic routing keys straight through from `deliver_extra` to the metadata dict. Webhook route configs are the only place the operator can express the opt-out, so this is the canonical place to wire them in. Mirrors `TelegramAdapter.send()` predicate precedence: `telegram_reply_to_message_id` (anchor) > `direct_messages_topic_id` (true Bot-API DM topic) > `telegram_dm_topic_created_for_send` (Hermes-created DM topic) > `telegram_dm_topic_reply_fallback` (reply_to_mode='off' opt-in). Legacy single-key `thread_id` / `message_thread_id` forwarding is unchanged.
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Telegram DM topic delivery now creates named topics on demand, refreshes stale thread ids, and refuses to silently leak private DM topic sends into General when the requested topic can't be honored. Salvages @stepanov1975's PR #27107 onto current main with conflict resolutions for parallel changes (transient-flake retry, reply_to_mode='off' opt-in).
Changes
gateway/delivery.py: route DM topic deliveries by mode — named string topics go throughensure_dm_topic+createForumTopic; numeric topics on private chats require a reply anchor or fail loud.gateway/platforms/telegram.py: newensure_dm_topic(force_create=)for on-demand topic creation + stale-thread refresh;_is_private_dm_topic_sendpredicate gates the new fail-loud paths insend();_persist_dm_topic_thread_idnow creates missing config path + supportsreplace_existing=.reply_to_mode='off'+telegram_dm_topic_reply_fallbackfrom the new anchor guard (preserves PR reply_to_mode: 'off'ignored by Telegram DM topic reply fallback code #23994 commit 21a15b6 user opt-in); switch one transient-flake retry test to-100123group chat so its scenario is unambiguous against the new contract.tests/conftest.py: blankAPI_SERVER_*env vars in the hermetic-conftest unset list.Behavioral change worth flagging
For numeric thread_ids on private DMs (positive chat_id), sends without a reply anchor now fail with
Telegram DM topic delivery requires a reply anchorinstead of silently stripping the thread_id and delivering to General. This is consistent with #31444 (don't hijack new DM topics) and aligns with the report Greg flagged in support-threads. Group forum topics, explicit Direct Messages topics, and thereply_to_mode='off'opt-in path are all exempt.tools/send_message_tool.pyhas its own_send_telegramretry loop (PR #27012) that retains the silent-fallback contract for cron/send_message callers; that path is not touched here.Validation
tests/gateway/tests/cron/+tests/tools/test_send_message_tool.pyCloses #27107 with credit to @stepanov1975.
Infographic