Summary
A Claude run can complete while the user receives no final Telegram message, and some SDK stream disconnects are incorrectly accepted as successful runs.
Confirmed failure modes
- Final Telegram replies do not retry transient
NetworkError/TimedOut failures. The current HTML-to-plain-text fallback retries immediately, so all attempts can fail inside the same outage window. AIORateLimiter only retries RetryAfter.
- The SDK receive loop can end without a
ResultMessage; this is currently treated as success instead of a connection failure.
builder.proxy() configures the normal Bot request client, while long-polling getUpdates uses its own client and is not given get_updates_proxy().
Proposed fix
- Add a small shared Telegram send helper with bounded exponential backoff for transient transport failures, while keeping parse-mode fallback separate.
- Require a terminal
ResultMessage; retry an empty/pre-output stream, but do not automatically replay a request after observable output/tool use because that could duplicate side effects.
- Apply an explicitly configured proxy to both Telegram request clients.
- Add regression tests for transient send recovery, terminal-message validation, and proxy wiring.
Out of scope
The global update lock currently serializes all users and can amplify a hung request. Converting it to per-session locking changes concurrency semantics and should be handled separately.
Related
Summary
A Claude run can complete while the user receives no final Telegram message, and some SDK stream disconnects are incorrectly accepted as successful runs.
Confirmed failure modes
NetworkError/TimedOutfailures. The current HTML-to-plain-text fallback retries immediately, so all attempts can fail inside the same outage window.AIORateLimiteronly retriesRetryAfter.ResultMessage; this is currently treated as success instead of a connection failure.builder.proxy()configures the normal Bot request client, while long-pollinggetUpdatesuses its own client and is not givenget_updates_proxy().Proposed fix
ResultMessage; retry an empty/pre-output stream, but do not automatically replay a request after observable output/tool use because that could duplicate side effects.Out of scope
The global update lock currently serializes all users and can amplify a hung request. Converting it to per-session locking changes concurrency semantics and should be handled separately.
Related