mirror of https://github.com/openclaw/openclaw.git
fix(telegram): add dnsResultOrder=ipv4first default on Node 22+ to fix fetch failures (#5405)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: 71366e9532
Co-authored-by: Glucksberg <80581902+Glucksberg@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
This commit is contained in:
parent
4e65e61612
commit
53adae9cec
|
|
@ -46,6 +46,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Security/Channels: harden Slack external menu token handling by switching to CSPRNG tokens, validating token shape, requiring user identity for external option lookups, and avoiding fabricated timestamp `trigger_id` fallbacks; also switch Tlon Urbit channel IDs to CSPRNG UUIDs, centralize secure ID/token generation via shared infra helpers, and add a guardrail test to block new runtime `Date.now()+Math.random()` token/id patterns.
|
||||
- Security/Hooks transforms: enforce symlink-safe containment for webhook transform module paths (including `hooks.transformsDir` and `hooks.mappings[].transform.module`) by resolving existing-path ancestors via realpath before import, while preserving in-root symlink support; add regression coverage for both escape and allow cases. This ships in the next npm release. Thanks @aether-ai-agent for reporting.
|
||||
- Telegram/WSL2: disable `autoSelectFamily` by default on WSL2 and memoize WSL2 detection in Telegram network decision logic to avoid repeated sync `/proc/version` probes on fetch/send paths. (#21916) Thanks @MizukiMachine.
|
||||
- Telegram/Network: default Node 22+ DNS result ordering to `ipv4first` for Telegram fetch paths and add `OPENCLAW_TELEGRAM_DNS_RESULT_ORDER`/`channels.telegram.network.dnsResultOrder` overrides to reduce IPv6-path fetch failures. (#5405) Thanks @Glucksberg.
|
||||
- Telegram/Streaming: preserve archived draft preview mapping after flush and clean superseded reasoning preview bubbles so multi-message preview finals no longer cross-edit or orphan stale messages under send/rotation races. (#23202) Thanks @obviyus.
|
||||
- Telegram/Polling: persist a safe update-offset watermark bounded by pending updates so crash/restart cannot skip queued lower `update_id` updates after out-of-order completion. (#23284) thanks @frankekn.
|
||||
- Slack/Slash commands: preserve the Bolt app receiver when registering external select options handlers so monitor startup does not crash on runtimes that require bound `app.options` calls. (#23209) Thanks @0xgaia.
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ export function resolveModel(
|
|||
const authStorage = discoverAuthStorage(resolvedAgentDir);
|
||||
const modelRegistry = discoverModels(authStorage, resolvedAgentDir);
|
||||
const model = modelRegistry.find(provider, modelId) as Model<Api> | null;
|
||||
|
||||
if (!model) {
|
||||
const providers = cfg?.models?.providers ?? {};
|
||||
const inlineModels = buildInlineProviderModels(providers);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@ export type TelegramActionConfig = {
|
|||
export type TelegramNetworkConfig = {
|
||||
/** Override Node's autoSelectFamily behavior (true = enable, false = disable). */
|
||||
autoSelectFamily?: boolean;
|
||||
/**
|
||||
* DNS result order for network requests ("ipv4first" | "verbatim").
|
||||
* Set to "ipv4first" to prioritize IPv4 addresses and work around IPv6 issues.
|
||||
* Default: "ipv4first" on Node 22+ to avoid common fetch failures.
|
||||
*/
|
||||
dnsResultOrder?: "ipv4first" | "verbatim";
|
||||
};
|
||||
|
||||
export type TelegramInlineButtonsScope = "off" | "dm" | "group" | "all" | "allowlist";
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ export const TelegramAccountSchemaBase = z
|
|||
network: z
|
||||
.object({
|
||||
autoSelectFamily: z.boolean().optional(),
|
||||
dnsResultOrder: z.enum(["ipv4first", "verbatim"]).optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { resolveFetch } from "../infra/fetch.js";
|
|||
import { resetTelegramFetchStateForTests, resolveTelegramFetch } from "./fetch.js";
|
||||
|
||||
const setDefaultAutoSelectFamily = vi.hoisted(() => vi.fn());
|
||||
const setDefaultResultOrder = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("node:net", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:net")>("node:net");
|
||||
|
|
@ -12,11 +13,20 @@ vi.mock("node:net", async () => {
|
|||
};
|
||||
});
|
||||
|
||||
vi.mock("node:dns", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:dns")>("node:dns");
|
||||
return {
|
||||
...actual,
|
||||
setDefaultResultOrder,
|
||||
};
|
||||
});
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
afterEach(() => {
|
||||
resetTelegramFetchStateForTests();
|
||||
setDefaultAutoSelectFamily.mockClear();
|
||||
setDefaultAutoSelectFamily.mockReset();
|
||||
setDefaultResultOrder.mockReset();
|
||||
vi.unstubAllEnvs();
|
||||
vi.clearAllMocks();
|
||||
if (originalFetch) {
|
||||
|
|
@ -105,4 +115,22 @@ describe("resolveTelegramFetch", () => {
|
|||
resolveTelegramFetch(undefined, { network: { autoSelectFamily: true } });
|
||||
expect(setDefaultAutoSelectFamily).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("applies dns result order from config", async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({})) as unknown as typeof fetch;
|
||||
resolveTelegramFetch(undefined, { network: { dnsResultOrder: "verbatim" } });
|
||||
expect(setDefaultResultOrder).toHaveBeenCalledWith("verbatim");
|
||||
});
|
||||
|
||||
it("retries dns setter on next call when previous attempt threw", async () => {
|
||||
setDefaultResultOrder.mockImplementationOnce(() => {
|
||||
throw new Error("dns setter failed once");
|
||||
});
|
||||
globalThis.fetch = vi.fn(async () => ({})) as unknown as typeof fetch;
|
||||
|
||||
resolveTelegramFetch(undefined, { network: { dnsResultOrder: "ipv4first" } });
|
||||
resolveTelegramFetch(undefined, { network: { dnsResultOrder: "ipv4first" } });
|
||||
|
||||
expect(setDefaultResultOrder).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,29 +1,50 @@
|
|||
import * as dns from "node:dns";
|
||||
import * as net from "node:net";
|
||||
import type { TelegramNetworkConfig } from "../config/types.telegram.js";
|
||||
import { resolveFetch } from "../infra/fetch.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { resolveTelegramAutoSelectFamilyDecision } from "./network-config.js";
|
||||
import {
|
||||
resolveTelegramAutoSelectFamilyDecision,
|
||||
resolveTelegramDnsResultOrderDecision,
|
||||
} from "./network-config.js";
|
||||
|
||||
let appliedAutoSelectFamily: boolean | null = null;
|
||||
let appliedDnsResultOrder: string | null = null;
|
||||
const log = createSubsystemLogger("telegram/network");
|
||||
|
||||
// Node 22 workaround: enable autoSelectFamily to allow IPv4 fallback on broken IPv6 networks.
|
||||
// Many networks have IPv6 configured but not routed, causing "Network is unreachable" errors.
|
||||
// See: https://github.com/nodejs/node/issues/54359
|
||||
function applyTelegramNetworkWorkarounds(network?: TelegramNetworkConfig): void {
|
||||
const decision = resolveTelegramAutoSelectFamilyDecision({ network });
|
||||
if (decision.value === null || decision.value === appliedAutoSelectFamily) {
|
||||
return;
|
||||
// Apply autoSelectFamily workaround
|
||||
const autoSelectDecision = resolveTelegramAutoSelectFamilyDecision({ network });
|
||||
if (autoSelectDecision.value !== null && autoSelectDecision.value !== appliedAutoSelectFamily) {
|
||||
if (typeof net.setDefaultAutoSelectFamily === "function") {
|
||||
try {
|
||||
net.setDefaultAutoSelectFamily(autoSelectDecision.value);
|
||||
appliedAutoSelectFamily = autoSelectDecision.value;
|
||||
const label = autoSelectDecision.source ? ` (${autoSelectDecision.source})` : "";
|
||||
log.info(`autoSelectFamily=${autoSelectDecision.value}${label}`);
|
||||
} catch {
|
||||
// ignore if unsupported by the runtime
|
||||
}
|
||||
}
|
||||
}
|
||||
appliedAutoSelectFamily = decision.value;
|
||||
|
||||
if (typeof net.setDefaultAutoSelectFamily === "function") {
|
||||
try {
|
||||
net.setDefaultAutoSelectFamily(decision.value);
|
||||
const label = decision.source ? ` (${decision.source})` : "";
|
||||
log.debug(`telegram: autoSelectFamily=${decision.value}${label}`);
|
||||
} catch {
|
||||
// ignore if unsupported by the runtime
|
||||
// Apply DNS result order workaround for IPv4/IPv6 issues.
|
||||
// Some APIs (including Telegram) may fail with IPv6 on certain networks.
|
||||
// See: https://github.com/openclaw/openclaw/issues/5311
|
||||
const dnsDecision = resolveTelegramDnsResultOrderDecision({ network });
|
||||
if (dnsDecision.value !== null && dnsDecision.value !== appliedDnsResultOrder) {
|
||||
if (typeof dns.setDefaultResultOrder === "function") {
|
||||
try {
|
||||
dns.setDefaultResultOrder(dnsDecision.value as "ipv4first" | "verbatim");
|
||||
appliedDnsResultOrder = dnsDecision.value;
|
||||
const label = dnsDecision.source ? ` (${dnsDecision.source})` : "";
|
||||
log.info(`dnsResultOrder=${dnsDecision.value}${label}`);
|
||||
} catch {
|
||||
// ignore if unsupported by the runtime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -46,4 +67,5 @@ export function resolveTelegramFetch(
|
|||
|
||||
export function resetTelegramFetchStateForTests(): void {
|
||||
appliedAutoSelectFamily = null;
|
||||
appliedDnsResultOrder = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|||
import {
|
||||
resetTelegramNetworkConfigStateForTests,
|
||||
resolveTelegramAutoSelectFamilyDecision,
|
||||
resolveTelegramDnsResultOrderDecision,
|
||||
} from "./network-config.js";
|
||||
|
||||
// Mock isWSL2Sync at the top level
|
||||
|
|
@ -129,3 +130,34 @@ describe("resolveTelegramAutoSelectFamilyDecision", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveTelegramDnsResultOrderDecision", () => {
|
||||
it("uses env override when provided", () => {
|
||||
const decision = resolveTelegramDnsResultOrderDecision({
|
||||
env: { OPENCLAW_TELEGRAM_DNS_RESULT_ORDER: "verbatim" },
|
||||
nodeMajor: 22,
|
||||
});
|
||||
expect(decision).toEqual({
|
||||
value: "verbatim",
|
||||
source: "env:OPENCLAW_TELEGRAM_DNS_RESULT_ORDER",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses config override when provided", () => {
|
||||
const decision = resolveTelegramDnsResultOrderDecision({
|
||||
network: { dnsResultOrder: "ipv4first" },
|
||||
nodeMajor: 20,
|
||||
});
|
||||
expect(decision).toEqual({ value: "ipv4first", source: "config" });
|
||||
});
|
||||
|
||||
it("defaults to ipv4first on Node 22", () => {
|
||||
const decision = resolveTelegramDnsResultOrderDecision({ nodeMajor: 22 });
|
||||
expect(decision).toEqual({ value: "ipv4first", source: "default-node22" });
|
||||
});
|
||||
|
||||
it("returns null when no dns decision applies", () => {
|
||||
const decision = resolveTelegramDnsResultOrderDecision({ nodeMajor: 20 });
|
||||
expect(decision).toEqual({ value: null });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { isWSL2Sync } from "../infra/wsl.js";
|
|||
export const TELEGRAM_DISABLE_AUTO_SELECT_FAMILY_ENV =
|
||||
"OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY";
|
||||
export const TELEGRAM_ENABLE_AUTO_SELECT_FAMILY_ENV = "OPENCLAW_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY";
|
||||
export const TELEGRAM_DNS_RESULT_ORDER_ENV = "OPENCLAW_TELEGRAM_DNS_RESULT_ORDER";
|
||||
|
||||
export type TelegramAutoSelectFamilyDecision = {
|
||||
value: boolean | null;
|
||||
|
|
@ -22,6 +23,11 @@ function isWSL2SyncCached(): boolean {
|
|||
return wsl2SyncCache;
|
||||
}
|
||||
|
||||
export type TelegramDnsResultOrderDecision = {
|
||||
value: string | null;
|
||||
source?: string;
|
||||
};
|
||||
|
||||
export function resolveTelegramAutoSelectFamilyDecision(params?: {
|
||||
network?: TelegramNetworkConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
|
|
@ -52,6 +58,49 @@ export function resolveTelegramAutoSelectFamilyDecision(params?: {
|
|||
return { value: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve DNS result order setting for Telegram network requests.
|
||||
* Some networks/ISPs have issues with IPv6 causing fetch failures.
|
||||
* Setting "ipv4first" prioritizes IPv4 addresses in DNS resolution.
|
||||
*
|
||||
* Priority:
|
||||
* 1. Environment variable OPENCLAW_TELEGRAM_DNS_RESULT_ORDER
|
||||
* 2. Config: channels.telegram.network.dnsResultOrder
|
||||
* 3. Default: "ipv4first" on Node 22+ (to work around common IPv6 issues)
|
||||
*/
|
||||
export function resolveTelegramDnsResultOrderDecision(params?: {
|
||||
network?: TelegramNetworkConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
nodeMajor?: number;
|
||||
}): TelegramDnsResultOrderDecision {
|
||||
const env = params?.env ?? process.env;
|
||||
const nodeMajor =
|
||||
typeof params?.nodeMajor === "number"
|
||||
? params.nodeMajor
|
||||
: Number(process.versions.node.split(".")[0]);
|
||||
|
||||
// Check environment variable
|
||||
const envValue = env[TELEGRAM_DNS_RESULT_ORDER_ENV]?.trim().toLowerCase();
|
||||
if (envValue === "ipv4first" || envValue === "verbatim") {
|
||||
return { value: envValue, source: `env:${TELEGRAM_DNS_RESULT_ORDER_ENV}` };
|
||||
}
|
||||
|
||||
// Check config
|
||||
const configValue = (params?.network as { dnsResultOrder?: string } | undefined)?.dnsResultOrder
|
||||
?.trim()
|
||||
.toLowerCase();
|
||||
if (configValue === "ipv4first" || configValue === "verbatim") {
|
||||
return { value: configValue, source: "config" };
|
||||
}
|
||||
|
||||
// Default to ipv4first on Node 22+ to avoid IPv6 issues
|
||||
if (Number.isFinite(nodeMajor) && nodeMajor >= 22) {
|
||||
return { value: "ipv4first", source: "default-node22" };
|
||||
}
|
||||
|
||||
return { value: null };
|
||||
}
|
||||
|
||||
export function resetTelegramNetworkConfigStateForTests(): void {
|
||||
wsl2SyncCache = undefined;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue