fix: handle Telegram network errors gracefully to prevent gateway crashes
- Expand recoverable error codes (ECONNABORTED, ERR_NETWORK) - Add message patterns for 'typeerror: fetch failed' and 'undici' errors - Add isNetworkRelatedError() helper for broad network failure detection - Retry on all network-related errors instead of crashing gateway - Remove unnecessary 'void' from fire-and-forget patterns - Add tests for new error patterns Fixes #3005
This commit is contained in:
@@ -322,7 +322,7 @@ export const registerTelegramNativeCommands = ({
|
||||
];
|
||||
|
||||
if (allCommands.length > 0) {
|
||||
void withTelegramApiErrorLogging({
|
||||
withTelegramApiErrorLogging({
|
||||
operation: "setMyCommands",
|
||||
runtime,
|
||||
fn: () => bot.api.setMyCommands(allCommands),
|
||||
@@ -576,7 +576,7 @@ export const registerTelegramNativeCommands = ({
|
||||
}
|
||||
}
|
||||
} else if (nativeDisabledExplicit) {
|
||||
void withTelegramApiErrorLogging({
|
||||
withTelegramApiErrorLogging({
|
||||
operation: "setMyCommands",
|
||||
runtime,
|
||||
fn: () => bot.api.setMyCommands([]),
|
||||
|
||||
@@ -74,6 +74,23 @@ const isGetUpdatesConflict = (err: unknown) => {
|
||||
return haystack.includes("getupdates");
|
||||
};
|
||||
|
||||
const NETWORK_ERROR_SNIPPETS = [
|
||||
"fetch failed",
|
||||
"network",
|
||||
"timeout",
|
||||
"socket",
|
||||
"econnreset",
|
||||
"econnrefused",
|
||||
"undici",
|
||||
];
|
||||
|
||||
const isNetworkRelatedError = (err: unknown) => {
|
||||
if (!err) return false;
|
||||
const message = formatErrorMessage(err).toLowerCase();
|
||||
if (!message) return false;
|
||||
return NETWORK_ERROR_SNIPPETS.some((snippet) => message.includes(snippet));
|
||||
};
|
||||
|
||||
export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) {
|
||||
const cfg = opts.config ?? loadConfig();
|
||||
const account = resolveTelegramAccount({
|
||||
@@ -158,7 +175,8 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) {
|
||||
}
|
||||
const isConflict = isGetUpdatesConflict(err);
|
||||
const isRecoverable = isRecoverableTelegramNetworkError(err, { context: "polling" });
|
||||
if (!isConflict && !isRecoverable) {
|
||||
const isNetworkError = isNetworkRelatedError(err);
|
||||
if (!isConflict && !isRecoverable && !isNetworkError) {
|
||||
throw err;
|
||||
}
|
||||
restartAttempts += 1;
|
||||
|
||||
@@ -8,6 +8,13 @@ describe("isRecoverableTelegramNetworkError", () => {
|
||||
expect(isRecoverableTelegramNetworkError(err)).toBe(true);
|
||||
});
|
||||
|
||||
it("detects additional recoverable error codes", () => {
|
||||
const aborted = Object.assign(new Error("aborted"), { code: "ECONNABORTED" });
|
||||
const network = Object.assign(new Error("network"), { code: "ERR_NETWORK" });
|
||||
expect(isRecoverableTelegramNetworkError(aborted)).toBe(true);
|
||||
expect(isRecoverableTelegramNetworkError(network)).toBe(true);
|
||||
});
|
||||
|
||||
it("detects AbortError names", () => {
|
||||
const err = Object.assign(new Error("The operation was aborted"), { name: "AbortError" });
|
||||
expect(isRecoverableTelegramNetworkError(err)).toBe(true);
|
||||
@@ -19,6 +26,11 @@ describe("isRecoverableTelegramNetworkError", () => {
|
||||
expect(isRecoverableTelegramNetworkError(err)).toBe(true);
|
||||
});
|
||||
|
||||
it("detects expanded message patterns", () => {
|
||||
expect(isRecoverableTelegramNetworkError(new Error("TypeError: fetch failed"))).toBe(true);
|
||||
expect(isRecoverableTelegramNetworkError(new Error("Undici: socket failure"))).toBe(true);
|
||||
});
|
||||
|
||||
it("skips message matches for send context", () => {
|
||||
const err = new TypeError("fetch failed");
|
||||
expect(isRecoverableTelegramNetworkError(err, { context: "send" })).toBe(false);
|
||||
|
||||
@@ -15,6 +15,8 @@ const RECOVERABLE_ERROR_CODES = new Set([
|
||||
"UND_ERR_BODY_TIMEOUT",
|
||||
"UND_ERR_SOCKET",
|
||||
"UND_ERR_ABORTED",
|
||||
"ECONNABORTED",
|
||||
"ERR_NETWORK",
|
||||
]);
|
||||
|
||||
const RECOVERABLE_ERROR_NAMES = new Set([
|
||||
@@ -27,6 +29,8 @@ const RECOVERABLE_ERROR_NAMES = new Set([
|
||||
|
||||
const RECOVERABLE_MESSAGE_SNIPPETS = [
|
||||
"fetch failed",
|
||||
"typeerror: fetch failed",
|
||||
"undici",
|
||||
"network error",
|
||||
"network request",
|
||||
"client network socket disconnected",
|
||||
|
||||
Reference in New Issue
Block a user