Bug
Since PR #3288 (bde45f5a — "retry transient send failures and notify user on exhaustion"), Telegram replies can be posted twice when a sendMessage call times out.
Root Cause
PR #3288 added _send_with_retry() to BasePlatformAdapter which retries on transient errors (including timeouts). However, TelegramAdapter.send() already has its own internal retry loop for network errors. This creates two stacked retry layers:
- Telegram adapter
send() retries internally (up to 3 attempts)
- Base
_send_with_retry() retries on top of that (up to 2 more attempts)
The critical problem: Telegram send timeouts are ambiguous. The Bot API may have already accepted and delivered the message even though the HTTP client timed out waiting for the response. When either retry layer fires after such a timeout, it re-sends the same content, producing user-visible duplicate messages.
Reproduction
- Send a message via Telegram adapter when the Bot API is slow (e.g., under load or flaky network)
- The
sendMessage call times out (ReadTimeout / TimedOut)
- Both the Telegram internal retry and the base retry wrapper fire
- The same message appears 2-3x in the chat
Suggested Fix
Two changes needed:
1. SendResult.delivery_uncertain flag in base.py
Add a delivery_uncertain: bool = False field to SendResult. When a platform returns delivery_uncertain=True, _send_with_retry() should immediately stop — no retries, no plain-text fallback.
2. Timeout detection + _send_with_retry override in telegram.py
- Add
_looks_like_send_timeout() to detect TimedOut, ReadTimeout, WriteTimeout exceptions
- When a send times out, return
SendResult(success=False, delivery_uncertain=True) instead of retrying
- Override
_send_with_retry() to bypass the base retry wrapper entirely, since Telegram send() already handles its own retry/fallback logic
This ensures that an ambiguous Telegram timeout stops all retry attempts immediately rather than risking duplicate delivery.
Affected Version
Any version including commit bde45f5a (PR #3288) onward.
Related
Bug
Since PR #3288 (
bde45f5a— "retry transient send failures and notify user on exhaustion"), Telegram replies can be posted twice when asendMessagecall times out.Root Cause
PR #3288 added
_send_with_retry()toBasePlatformAdapterwhich retries on transient errors (including timeouts). However,TelegramAdapter.send()already has its own internal retry loop for network errors. This creates two stacked retry layers:send()retries internally (up to 3 attempts)_send_with_retry()retries on top of that (up to 2 more attempts)The critical problem: Telegram send timeouts are ambiguous. The Bot API may have already accepted and delivered the message even though the HTTP client timed out waiting for the response. When either retry layer fires after such a timeout, it re-sends the same content, producing user-visible duplicate messages.
Reproduction
sendMessagecall times out (ReadTimeout / TimedOut)Suggested Fix
Two changes needed:
1.
SendResult.delivery_uncertainflag inbase.pyAdd a
delivery_uncertain: bool = Falsefield toSendResult. When a platform returnsdelivery_uncertain=True,_send_with_retry()should immediately stop — no retries, no plain-text fallback.2. Timeout detection +
_send_with_retryoverride intelegram.py_looks_like_send_timeout()to detectTimedOut,ReadTimeout,WriteTimeoutexceptionsSendResult(success=False, delivery_uncertain=True)instead of retrying_send_with_retry()to bypass the base retry wrapper entirely, since Telegramsend()already handles its own retry/fallback logicThis ensures that an ambiguous Telegram timeout stops all retry attempts immediately rather than risking duplicate delivery.
Affected Version
Any version including commit
bde45f5a(PR #3288) onward.Related