From a3f91450054e1183b04cb3e38cb118424ee75815 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Mar 2026 13:00:06 +0000 Subject: [PATCH] fix(telegramSafe): exempt getUpdates and lifecycle calls from 15s timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Telegraf's long-polling uses getUpdates with timeout=50 (Telegram holds the connection for up to 50 seconds waiting for new updates). The callApi patch was wrapping ALL methods — including getUpdates — with globalThrottle, which has a hard 15-second race. This fired on every poll cycle, throwing 'rateLimiter: API call timed out after 15000ms', causing bot.launch() to reject and systemd to enter an infinite restart loop. Fix: add getUpdates (and Telegraf startup/lifecycle calls: getMe, deleteWebhook, setWebhook, getWebhookInfo, close, logOut) to the _MANAGED bypass set so they call the original callApi directly without the timeout wrapper. All 60 unit tests pass. Bot loads cleanly in CI mode. https://claude.ai/code/session_01WpEqAiDbpqnBywMjYXJHf4 --- telegramSafe.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/telegramSafe.js b/telegramSafe.js index 2d7aa45..4c58204 100644 --- a/telegramSafe.js +++ b/telegramSafe.js @@ -180,9 +180,24 @@ function init(bot) { // layer and pass through to the original. if (typeof tg.callApi === 'function') { const _MANAGED = new Set([ + // ── Individually-patched above — skip double-wrap ──────────────── 'sendMessage', 'editMessageText', 'answerCbQuery', 'answerCallbackQuery', 'sendPhoto', 'sendDocument', 'sendAnimation', 'sendSticker', 'forwardMessage', + // ── Long-poll — MUST bypass the 15-second API_CALL_TIMEOUT_MS ──── + // Telegraf passes timeout=50 to getUpdates so Telegram holds the + // connection open for up to 50 s. Wrapping it with globalThrottle's + // 15-second race fires on every poll cycle and kills the bot. + 'getUpdates', + // ── Startup / lifecycle calls — fast one-shots ─────────────────── + // Pass through directly so they are never blocked behind a queued + // getUpdates slot in the global rate-limit chain. + 'getMe', + 'deleteWebhook', + 'setWebhook', + 'getWebhookInfo', + 'close', + 'logOut', ]); const _callApi = tg.callApi.bind(tg); tg.callApi = (method, data, signal) => {