From 2f023a4775816ae4b5a1b273c6566fd6f31e39b3 Mon Sep 17 00:00:00 2001 From: miz-cha Date: Sun, 22 Feb 2026 14:24:49 +0900 Subject: [PATCH] fix(telegram): disable autoSelectFamily by default on WSL2 (#21916) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 431fd966706e300a378b177b25b00af952eddc8b Co-authored-by: MizukiMachine <185313792+MizukiMachine@users.noreply.github.com> Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com> Reviewed-by: @obviyus --- CHANGELOG.md | 1 + src/telegram/network-config.test.ts | 63 ++++++++++++++++++++++++++++- src/telegram/network-config.ts | 19 +++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80c739002f2..e909131af32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- 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/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. - 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. - Slack/Telegram slash sessions: await session metadata persistence before dispatch so first-turn native slash runs do not race session-origin metadata updates. (#23065) thanks @hydro13. diff --git a/src/telegram/network-config.test.ts b/src/telegram/network-config.test.ts index be89b5ea8e9..e8abe83efef 100644 --- a/src/telegram/network-config.test.ts +++ b/src/telegram/network-config.test.ts @@ -1,7 +1,22 @@ -import { describe, expect, it } from "vitest"; -import { resolveTelegramAutoSelectFamilyDecision } from "./network-config.js"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { + resetTelegramNetworkConfigStateForTests, + resolveTelegramAutoSelectFamilyDecision, +} from "./network-config.js"; + +// Mock isWSL2Sync at the top level +vi.mock("../infra/wsl.js", () => ({ + isWSL2Sync: vi.fn(() => false), +})); + +import { isWSL2Sync } from "../infra/wsl.js"; describe("resolveTelegramAutoSelectFamilyDecision", () => { + afterEach(() => { + vi.restoreAllMocks(); + resetTelegramNetworkConfigStateForTests(); + }); + it("prefers env enable over env disable", () => { const decision = resolveTelegramAutoSelectFamilyDecision({ env: { @@ -69,4 +84,48 @@ describe("resolveTelegramAutoSelectFamilyDecision", () => { const decision = resolveTelegramAutoSelectFamilyDecision({ env: {}, nodeMajor: 20 }); expect(decision).toEqual({ value: null }); }); + + describe("WSL2 detection", () => { + it("disables autoSelectFamily on WSL2", () => { + vi.mocked(isWSL2Sync).mockReturnValue(true); + const decision = resolveTelegramAutoSelectFamilyDecision({ env: {}, nodeMajor: 22 }); + expect(decision).toEqual({ value: false, source: "default-wsl2" }); + }); + + it("respects config override on WSL2", () => { + vi.mocked(isWSL2Sync).mockReturnValue(true); + const decision = resolveTelegramAutoSelectFamilyDecision({ + env: {}, + network: { autoSelectFamily: true }, + nodeMajor: 22, + }); + expect(decision).toEqual({ value: true, source: "config" }); + }); + + it("respects env override on WSL2", () => { + vi.mocked(isWSL2Sync).mockReturnValue(true); + const decision = resolveTelegramAutoSelectFamilyDecision({ + env: { OPENCLAW_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY: "1" }, + nodeMajor: 22, + }); + expect(decision).toEqual({ + value: true, + source: "env:OPENCLAW_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY", + }); + }); + + it("uses Node 22 default when not on WSL2", () => { + vi.mocked(isWSL2Sync).mockReturnValue(false); + const decision = resolveTelegramAutoSelectFamilyDecision({ env: {}, nodeMajor: 22 }); + expect(decision).toEqual({ value: true, source: "default-node22" }); + }); + + it("memoizes WSL2 detection across repeated defaults", () => { + vi.mocked(isWSL2Sync).mockReset(); + vi.mocked(isWSL2Sync).mockReturnValue(false); + resolveTelegramAutoSelectFamilyDecision({ env: {}, nodeMajor: 22 }); + resolveTelegramAutoSelectFamilyDecision({ env: {}, nodeMajor: 22 }); + expect(isWSL2Sync).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/src/telegram/network-config.ts b/src/telegram/network-config.ts index 86b44fe59eb..27815e8d8f4 100644 --- a/src/telegram/network-config.ts +++ b/src/telegram/network-config.ts @@ -1,6 +1,7 @@ import process from "node:process"; import type { TelegramNetworkConfig } from "../config/types.telegram.js"; import { isTruthyEnvValue } from "../infra/env.js"; +import { isWSL2Sync } from "../infra/wsl.js"; export const TELEGRAM_DISABLE_AUTO_SELECT_FAMILY_ENV = "OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY"; @@ -11,6 +12,16 @@ export type TelegramAutoSelectFamilyDecision = { source?: string; }; +let wsl2SyncCache: boolean | undefined; + +function isWSL2SyncCached(): boolean { + if (typeof wsl2SyncCache === "boolean") { + return wsl2SyncCache; + } + wsl2SyncCache = isWSL2Sync(); + return wsl2SyncCache; +} + export function resolveTelegramAutoSelectFamilyDecision(params?: { network?: TelegramNetworkConfig; env?: NodeJS.ProcessEnv; @@ -31,8 +42,16 @@ export function resolveTelegramAutoSelectFamilyDecision(params?: { if (typeof params?.network?.autoSelectFamily === "boolean") { return { value: params.network.autoSelectFamily, source: "config" }; } + // WSL2 has unstable IPv6 connectivity; disable autoSelectFamily to use IPv4 directly + if (isWSL2SyncCached()) { + return { value: false, source: "default-wsl2" }; + } if (Number.isFinite(nodeMajor) && nodeMajor >= 22) { return { value: true, source: "default-node22" }; } return { value: null }; } + +export function resetTelegramNetworkConfigStateForTests(): void { + wsl2SyncCache = undefined; +}