Skip to content

fix(telegram): prevent duplicate messages on send timeout#3922

Closed
dlkakbs wants to merge 1 commit into
NousResearch:mainfrom
dlkakbs:fix/telegram-duplicate-on-send-timeout
Closed

fix(telegram): prevent duplicate messages on send timeout#3922
dlkakbs wants to merge 1 commit into
NousResearch:mainfrom
dlkakbs:fix/telegram-duplicate-on-send-timeout

Conversation

@dlkakbs

@dlkakbs dlkakbs commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Problem

Closes #3906.

PR #3288 added _send_with_retry() to BasePlatformAdapter, which retries on transient errors including timeouts. TelegramAdapter.send() already has its own internal 3-attempt retry loop for network errors. This created two stacked retry layers.

The critical issue: a Telegram sendMessage timeout is ambiguous — the Bot API may have already accepted and delivered the message even though the HTTP client got no response. When either retry layer fired after such a timeout, it re-sent the same content, producing 2–3 duplicate messages.

Fix

gateway/platforms/base.py

  • Add SendResult.delivery_uncertain: bool = False field
  • In _send_with_retry(), return immediately (no retry, no plain-text fallback) when result.delivery_uncertain is True

gateway/platforms/telegram.py

  • Add _looks_like_send_timeout() static method — detects TimedOut, ReadTimeout, WriteTimeout by isinstance check and by class name, so it works with and without the python-telegram-bot import
  • In send(), set delivery_uncertain=True when the final caught exception is a send timeout

What is NOT changed

The internal for _send_attempt in range(3) loop inside send() retries on NetworkError (including TimedOut). This pre-existed PR #3288 and is out of scope for this fix.

Tests

tests/gateway/test_telegram_send_timeout.py — 8 new tests:

  • _looks_like_send_timeout() detects TimedOut instance, class-name matches (ReadTimeout, WriteTimeout), and correctly ignores non-timeout NetworkError
  • send() sets delivery_uncertain=True on timeout, False on other errors
  • _send_with_retry() calls send() exactly once when delivery_uncertain=True
  • _send_with_retry() still retries normally on non-uncertain network errors

All 8 new tests pass. All 102 existing Telegram tests unaffected.

When sendMessage times out, the Bot API may have already delivered the
message even though the HTTP client got no response.  PR NousResearch#3288 added
_send_with_retry() which retried on any transient error (including
timeouts), stacking on top of TelegramAdapter.send()'s existing 3-
attempt internal loop — risking 2–3 duplicate messages per response.

- Add SendResult.delivery_uncertain flag; when True, _send_with_retry()
  returns immediately without retrying or falling back to plain text.
- Add TelegramAdapter._looks_like_send_timeout() to detect TimedOut /
  ReadTimeout / WriteTimeout exceptions (with and without the
  python-telegram-bot import).
- Set delivery_uncertain=True in send()'s final except clause when the
  exhausted error is a send timeout.

Fixes NousResearch#3906.
@dlkakbs

dlkakbs commented Mar 30, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favor of #3899 which also addresses the inner retry loop - my delivery_uncertain approach only stops the outer layer.

Thanks! @dieutx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Telegram duplicate messages caused by stacked retry layers after send timeout

1 participant