fix(telegram): use idempotent retry context for delete/reaction (#96612)

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 <noreply@anthropic.com>
This commit is contained in:
miorbnli
2026-06-28 00:31:52 +08:00
committed by GitHub
parent e5c3c59c67
commit ce15f348bb
3 changed files with 19 additions and 3 deletions

View File

@@ -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(

View File

@@ -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;

View File

@@ -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", {