← Back to PRs

#19050: fix(telegram): skip message_thread_id for private chats to prevent silent message drops (v2)

by Limitless2023 open 2026-02-17 08:51 View on GitHub →
channel: telegram size: S
## Improved v2 of #17252 — Fixes #17242 Closes #17242 ### Problem When sending messages to Telegram **private chats** (positive chatId), the opts-level `messageThreadId` — typically derived from reply-context — is passed as `message_thread_id` to the Telegram API. Private chats don't support forum topics, so the API rejects with: ``` 400: Bad Request: message thread not found ``` Messages are **silently dropped**, affecting: - Sub-agent announce notifications (`sessions_spawn`) - Cron job deliveries to private chats - Proactive `message` tool sends ### Root cause `buildTelegramThreadReplyParams` unconditionally merges `opts.messageThreadId` into the thread spec for all chat types, including private chats where `message_thread_id` is invalid. ### Fix In `buildTelegramThreadReplyParams`, private chats (`chatType === "direct"`) now **only honour the thread ID embedded in the target address** (e.g., `123456789:topic:42`) and **ignore the opts-level `messageThreadId`**. This is a single-point fix that covers **all send paths**: text messages, media, stickers, and polls — addressing the reviewer feedback on #17252 about missing sticker/poll checks. ### Why not blanket-strip? [Previous #17252 was rejected](https://github.com/openclaw/openclaw/pull/17252) because blanket-suppressing `message_thread_id` for all private chats would break **DM topic routing** (Bot API 9.3, #18974). This v2 distinguishes between: | Source | Private chat behaviour | Rationale | |--------|----------------------|-----------| | Target address (`123456789:topic:42`) | ✅ **Kept** | Intentional DM topic routing | | `opts.messageThreadId` (reply context) | ❌ **Skipped** | Reply-context thread IDs don't apply to plain DMs | ### Changes - **`src/telegram/send.ts`** — `buildTelegramThreadReplyParams`: for private chats, use only `targetMessageThreadId` (from target string), ignore `opts.messageThreadId` - **`src/telegram/send.test.ts`** — Updated/added tests: - Verifies opts-level `messageThreadId` is skipped for private chat text sends - Verifies target-embedded thread IDs are preserved for DM topics - Verifies opts-level `messageThreadId` is skipped for private chat sticker sends - Updated chat-not-found test to use group chatId (was using private chatId with now-skipped thread param) ### Test results All 38 telegram test files pass (469 tests). <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR fixes silent message drops when sending to Telegram private chats with a reply-context `messageThreadId` (#17242). The root cause was `buildTelegramThreadReplyParams` unconditionally forwarding `opts.messageThreadId` (from reply context) to the API as `message_thread_id`, which private chats reject with `400: Bad Request: message thread not found`. The fix is a single-point change in `buildTelegramThreadReplyParams`: - **Private chats** (`chatType === "direct"`): only use the thread ID embedded in the target address (e.g., `123456789:topic:42`), ignoring the opts-level `messageThreadId` - **Group/unknown chats**: preserve the existing precedence (`opts.messageThreadId` takes priority, falls back to `targetMessageThreadId`) This covers all send paths (text, media, stickers, polls) since they all flow through `buildTelegramThreadReplyParams`. - Tests updated to verify opts-level thread ID is skipped for private text and sticker sends - New test verifies target-embedded thread IDs are preserved for DM topic routing (Bot API 9.3) - Existing chat-not-found test updated to use group chat ID (since private chats now skip opts thread ID, the old assertion was invalid) <h3>Confidence Score: 5/5</h3> - This PR is safe to merge — it's a well-scoped fix with correct logic and thorough test coverage. - The change is minimal (6 lines of logic in one function), the fix is correct based on Telegram API semantics, it preserves existing behavior for non-private chats, and the tests comprehensively cover the key scenarios: opts-level thread ID skipped for private chats, target-embedded thread ID preserved for DM topics, and group chat behavior unchanged. The `??` operator is semantically equivalent to the previous `!= null` ternary for the non-private path. - No files require special attention. <sub>Last reviewed commit: 3cd7e0d</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs