← Back to PRs

#19479: fix(telegram): skip redundant final edit in partial streaming mode

by v8hid open 2026-02-17 21:15 View on GitHub →
channel: telegram size: S
## Summary - Problem: In draft streaming, Telegram finalization would attempt editMessageText with unchanged content. - Why it matters: Telegram rejects same-content edits (`message is not modified`), which trigger failover cleanup/send and create duplicate visible messages plus error logs. - What changed: Added `lastAppliedText()` tracking in `draft-stream.ts` and used it in `bot-message-dispatch.ts` to short-circuit unchanged final edits when no mutation is needed. - Kept final edit behavior for real mutations (formatting pass, inline buttons, `linkPreview=false`) and expanded tests. Rebased cleanly onto main — turns out merging main into a feature branch makes the bot think you're trying to smuggle 200 unrelated commits into your PR. Lesson learned. 2 clean commits now, as intended. Replaces #17766 (auto-closed by bot after a merge commit polluted the history). ## Change Type - [x] Bug fix ## Scope - [x] Integrations ## Related Fix Commit [ac2ede5](https://github.com/openclaw/openclaw/commit/ac2ede5bb) by @steipete already treats `message is not modified` API errors as success. This PR adds a complementary optimization that prevents the unnecessary API call from being attempted in the first place. Both fixes work together: - `ac2ede5bb`: Reactive safety net at the API error handling layer - This PR: Proactive optimization at the logic layer to avoid the no-op API call entirely <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR adds a proactive optimization to skip the redundant `editMessageText` Telegram API call when the preview stream already shows the exact final text, complementing the existing reactive error-handling safety net. The approach is sound — adding `lastSentText()` to `TelegramDraftStream` and checking it in `bot-message-dispatch.ts` before attempting the final edit. The skip conditions correctly account for formatting passes (`renderTelegramHtmlText` comparison), inline button mutations, and `linkPreview=false` mutations. **Key issue found:** - `lastSentText` is assigned **optimistically before the API call** (line 78 of `draft-stream.ts`). When `editMessageText` or `sendMessage` throws, `lastSentText` already reflects the attempted text, not the last successfully delivered text. This contradicts the test assertion added in `draft-stream.test.ts` (`"keeps lastSentText at the last successful value when an edit fails"`) — that test will fail against the current implementation. - More critically, this creates a correctness gap in the dispatch optimization: if a mid-stream edit fails (e.g. network error), `lastSentText` equals the failed-attempt text. If the final text also equals that value, the final edit is skipped, leaving the Telegram message showing the last *successfully delivered* (older) content rather than the final text. - The fix is straightforward: save the previous `lastSentText` value before the assignment, and restore it in the catch block (and in the `sendMessage` early-return path). **Everything else looks good:** - The `needsFormattingPass` check using `renderTelegramHtmlText` is correct and matches how `editMessageTelegram` renders text internally. - The `hasFinalEditMutation` check for buttons and `linkPreview` correctly identifies cases requiring the final edit. - Test coverage in `bot-message-dispatch.test.ts` is comprehensive (plain text match, formatting pass, buttons, linkPreview). - The `lastSentText: () => string | undefined` type is slightly wider than the actual `""` initialization (always returns `string`), but this is harmless. <h3>Confidence Score: 3/5</h3> - Not safe to merge as-is: the new draft-stream test will fail, and the optimization has a correctness gap when a streaming edit fails mid-stream. - The `lastSentText` variable is set before the API call (optimistic update), but the test and the dispatch optimization require it to reflect the last *confirmed* delivery. A failing streaming edit leaves `lastSentText` pointing to the undelivered text, allowing the final edit to be incorrectly skipped. The new `draft-stream.test.ts` test ("keeps lastSentText at the last successful value when an edit fails") will fail against the current implementation. Fixing it requires restoring `lastSentText` to its prior value in the catch block. - src/telegram/draft-stream.ts — the `sendOrEditStreamMessage` function needs to restore `lastSentText` on API failure. <sub>Last reviewed commit: 1999e63</sub> <!-- greptile_other_comments_section --> <sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub> <!-- /greptile_comment -->

Most Similar PRs