mirror of https://github.com/openclaw/openclaw.git
fix(telegram): classify undici fetch errors as recoverable for retry (#7365)
This commit is contained in:
parent
38f02c7a32
commit
0452791c63
|
|
@ -670,6 +670,25 @@ openclaw message send --channel telegram --target @name --message "hi"
|
|||
|
||||
- Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch.
|
||||
- Some hosts resolve `api.telegram.org` to IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures.
|
||||
- If logs include `TypeError: fetch failed` or `Network request for 'getUpdates' failed!`, OpenClaw now retries these as recoverable network errors.
|
||||
- On VPS hosts with unstable direct egress/TLS, route Telegram API calls through `channels.telegram.proxy`:
|
||||
|
||||
```yaml
|
||||
channels:
|
||||
telegram:
|
||||
proxy: socks5://user:pass@proxy-host:1080
|
||||
```
|
||||
|
||||
- If DNS/IPv6 selection is unstable, force Node family selection behavior explicitly:
|
||||
|
||||
```yaml
|
||||
channels:
|
||||
telegram:
|
||||
network:
|
||||
autoSelectFamily: false
|
||||
```
|
||||
|
||||
- Environment override (temporary): set `OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1`.
|
||||
- Validate DNS answers:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -169,8 +169,12 @@ describe("monitorTelegramProvider (grammY)", () => {
|
|||
expect(api.sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("retries on recoverable network errors", async () => {
|
||||
const networkError = Object.assign(new Error("timeout"), { code: "ETIMEDOUT" });
|
||||
it("retries on recoverable undici fetch errors", async () => {
|
||||
const networkError = Object.assign(new TypeError("fetch failed"), {
|
||||
cause: Object.assign(new Error("connect timeout"), {
|
||||
code: "UND_ERR_CONNECT_TIMEOUT",
|
||||
}),
|
||||
});
|
||||
runSpy
|
||||
.mockImplementationOnce(() => ({
|
||||
task: () => Promise.reject(networkError),
|
||||
|
|
|
|||
|
|
@ -30,8 +30,17 @@ describe("isRecoverableTelegramNetworkError", () => {
|
|||
expect(isRecoverableTelegramNetworkError(new Error("Undici: socket failure"))).toBe(true);
|
||||
});
|
||||
|
||||
it("skips message matches for send context", () => {
|
||||
it("treats undici fetch failed errors as recoverable in send context", () => {
|
||||
const err = new TypeError("fetch failed");
|
||||
expect(isRecoverableTelegramNetworkError(err, { context: "send" })).toBe(true);
|
||||
expect(
|
||||
isRecoverableTelegramNetworkError(new Error("TypeError: fetch failed"), { context: "send" }),
|
||||
).toBe(true);
|
||||
expect(isRecoverableTelegramNetworkError(err, { context: "polling" })).toBe(true);
|
||||
});
|
||||
|
||||
it("skips broad message matches for send context", () => {
|
||||
const err = new Error("Network request for 'sendMessage' failed!");
|
||||
expect(isRecoverableTelegramNetworkError(err, { context: "send" })).toBe(false);
|
||||
expect(isRecoverableTelegramNetworkError(err, { context: "polling" })).toBe(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,6 +40,13 @@ const RECOVERABLE_MESSAGE_SNIPPETS = [
|
|||
"timed out", // grammY getUpdates returns "timed out after X seconds" (not matched by "timeout")
|
||||
];
|
||||
|
||||
// Undici surface errors as TypeError("fetch failed") with optional nested causes.
|
||||
// Treat this exact shape as recoverable even when broad message matching is disabled.
|
||||
function isUndiciFetchFailedError(err: unknown): boolean {
|
||||
const message = formatErrorMessage(err).trim().toLowerCase();
|
||||
return message === "fetch failed" || message === "typeerror: fetch failed";
|
||||
}
|
||||
|
||||
function normalizeCode(code?: string): string {
|
||||
return code?.trim().toUpperCase() ?? "";
|
||||
}
|
||||
|
|
@ -128,6 +135,10 @@ export function isRecoverableTelegramNetworkError(
|
|||
: options.context !== "send";
|
||||
|
||||
for (const candidate of collectErrorCandidates(err)) {
|
||||
if (isUndiciFetchFailedError(candidate)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const code = normalizeCode(getErrorCode(candidate));
|
||||
if (code && RECOVERABLE_ERROR_CODES.has(code)) {
|
||||
return true;
|
||||
|
|
|
|||
Loading…
Reference in New Issue