From ce15f348bbc419e91c8ab0034cf2a06382f5e912 Mon Sep 17 00:00:00 2001 From: miorbnli Date: Sun, 28 Jun 2026 00:31:52 +0800 Subject: [PATCH] fix(telegram): use idempotent retry context for delete/reaction (#96612) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reactMessageTelegram and deleteMessageTelegram passed context: "send" to isRecoverableTelegramNetworkError, which disables message-snippet matching (allowMessageMatch defaults to false only for "send"). Both operations are idempotent (setMessageReaction / deleteMessage are safe to repeat), yet a transient snippet-only network error (e.g. "socket hang up", "undici network error" with no error code) was not retried — stricter than polling/webhook/ unknown, which all default allowMessageMatch to true. Users saw spurious reaction/delete failures on transient network errors. Add delete | react to TelegramNetworkErrorContext (additive) and use them at the two callers. The helper default (context !== "send") is unchanged, so delete/react now match polling/webhook/unknown. sendMessage keeps "send". Co-authored-by: Claude --- extensions/telegram/src/network-errors.test.ts | 10 ++++++++++ extensions/telegram/src/network-errors.ts | 8 +++++++- extensions/telegram/src/send.ts | 4 ++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/extensions/telegram/src/network-errors.test.ts b/extensions/telegram/src/network-errors.test.ts index d57380524da..498fd5418a1 100644 --- a/extensions/telegram/src/network-errors.test.ts +++ b/extensions/telegram/src/network-errors.test.ts @@ -139,6 +139,16 @@ describe("isRecoverableTelegramNetworkError", () => { expect(isRecoverableTelegramNetworkError(undiciSnippetErr, { context: "polling" })).toBe(true); }); + it("treats delete/react (idempotent) contexts like polling, not send", () => { + const undiciSnippetErr = new Error("Undici: socket failure"); + // delete and react are idempotent Telegram operations; a transient + // snippet-only error must be retried (allowMessageMatch defaults true), + // matching polling/webhook. send stays strict as the regression guard. + expect(isRecoverableTelegramNetworkError(undiciSnippetErr, { context: "delete" })).toBe(true); + expect(isRecoverableTelegramNetworkError(undiciSnippetErr, { context: "react" })).toBe(true); + expect(isRecoverableTelegramNetworkError(undiciSnippetErr, { context: "send" })).toBe(false); + }); + it("treats grammY failed-after envelope errors as recoverable in send context", () => { expect( isRecoverableTelegramNetworkError( diff --git a/extensions/telegram/src/network-errors.ts b/extensions/telegram/src/network-errors.ts index d8ade9fdffa..9fd24633f9d 100644 --- a/extensions/telegram/src/network-errors.ts +++ b/extensions/telegram/src/network-errors.ts @@ -141,7 +141,13 @@ export function isTelegramMisdirectedRequestError(err: unknown): boolean { return false; } -export type TelegramNetworkErrorContext = "polling" | "send" | "webhook" | "unknown"; +export type TelegramNetworkErrorContext = + | "polling" + | "send" + | "webhook" + | "delete" + | "react" + | "unknown"; export type TelegramNetworkErrorOrigin = { method?: string | null; url?: string | null; diff --git a/extensions/telegram/src/send.ts b/extensions/telegram/src/send.ts index 33adeeffdb8..6ae8891fc86 100644 --- a/extensions/telegram/src/send.ts +++ b/extensions/telegram/src/send.ts @@ -1219,7 +1219,7 @@ export async function reactMessageTelegram( account, retry: opts.retry, verbose: opts.verbose, - shouldRetry: (err) => isRecoverableTelegramNetworkError(err, { context: "send" }), + shouldRetry: (err) => isRecoverableTelegramNetworkError(err, { context: "react" }), }); const remove = opts.remove === true; const trimmedEmoji = emoji.trim(); @@ -1276,7 +1276,7 @@ export async function deleteMessageTelegram( account, retry: opts.retry, verbose: opts.verbose, - shouldRetry: (err) => isRecoverableTelegramNetworkError(err, { context: "send" }), + shouldRetry: (err) => isRecoverableTelegramNetworkError(err, { context: "delete" }), }); try { await requestWithDiag(() => api.deleteMessage(chatId, messageId), "deleteMessage", {