← Back to PRs

#7247: fix(telegram): abort stale getUpdates connections after long-poll timeout

by JanderV open 2026-02-02 16:06 View on GitHub →
channel: telegram
## Problem After system sleep or network disruptions, Telegram long-poll `getUpdates` requests can hang on stale TCP connections for up to **500 seconds** (the default HTTP timeout). During this time the bot appears completely unresponsive — no messages are received or processed. Additionally, when these requests finally time out, grammY throws `"timed out"` but `NETWORK_ERROR_SNIPPETS` only contained `"timeout"`, so the error was not recognized as recoverable and the polling monitor exited permanently. Fixes #4617. Supersedes #6999. ## Solution ### 1. AbortController-based fetch timeout (`fetch.ts`) - New `wrapFetchWithTimeout()` wraps the fetch implementation with an `AbortController` + `setTimeout` - **Only applies to `getUpdates` requests** via `isTelegramGetUpdatesRequest()` filter — uploads and other API calls are unaffected - Properly composes with any existing caller-provided `AbortSignal` - Preserves the `preconnect` property on wrapped fetch functions ### 2. Long-poll timeout resolution (`monitor.ts`) - `resolveTelegramLongPollTimeoutMs()` derives the abort timeout from the runner's configured `fetch.timeout` + a 10-second grace period (`TELEGRAM_LONG_POLL_GRACE_MS`) - Runner options are now computed once before the restart loop and reused across iterations ### 3. Network error snippet fix (`monitor.ts`) - Added `"timed out"` to `NETWORK_ERROR_SNIPPETS` so the abort error is correctly classified as recoverable and triggers a retry instead of a permanent exit ## Testing - **"aborts requests after timeoutMs"** — verifies the fetch wrapper aborts getUpdates after the configured timeout using fake timers - **"skips timeout for non-getUpdates requests"** — verifies non-getUpdates requests pass through without an abort signal - All 361 existing Telegram tests continue to pass ## Files changed - `src/telegram/fetch.ts` — `wrapFetchWithTimeout()`, `isTelegramGetUpdatesRequest()`, `resolveFetchUrl()` - `src/telegram/monitor.ts` — `resolveTelegramLongPollTimeoutMs()`, snippet fix, runner options reuse - `src/telegram/bot.ts` — `longPollTimeoutMs` option threading - `src/telegram/fetch.test.ts` — 2 new tests <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR adds an AbortController-based timeout wrapper around Telegram fetch calls, scoped to long-poll `getUpdates`, and wires the derived timeout (runner fetch timeout + 10s grace) through the Telegram bot creation path. It also expands recoverable network error detection by including the grammY/undici-style message snippet "timed out", and reuses computed runner options across polling restarts. Overall, this fits cleanly into the existing Telegram provider structure: `monitor.ts` owns runner lifecycle and restart policy, `bot.ts` wires config into grammY’s client, and `fetch.ts` centralizes runtime/network fetch behavior. <h3>Confidence Score: 4/5</h3> - This PR looks safe to merge; changes are localized and covered by targeted tests. - Main behavior change is a scoped abort timeout for `getUpdates` only, plus a small tweak to network error classification. The main remaining risk is overly-broad URL matching for `getUpdates` detection and edge cases with non-standard fetch implementations. - src/telegram/fetch.ts (URL matching and timeout wrapper robustness) <!-- 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