Skip to content

Commit 33991d9

Browse files
MarkShawn2020claude
andcommitted
Save and restore original dispatcher on true→false transition
Address review feedback: when autoSelectFamily switches from true to false, restore the original global dispatcher instead of leaving the replacement sticky for the process lifetime. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 90dfbdc commit 33991d9

File tree

2 files changed

+45
-22
lines changed

2 files changed

+45
-22
lines changed

src/telegram/fetch.test.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,13 @@ vi.mock("node:dns", async () => {
2727
};
2828
});
2929

30+
const mockOriginalDispatcher = vi.hoisted(() => ({ __original: true }));
31+
const getGlobalDispatcher = vi.hoisted(() => vi.fn(() => mockOriginalDispatcher));
32+
3033
vi.mock("undici", () => ({
3134
Agent: AgentCtor,
3235
setGlobalDispatcher,
36+
getGlobalDispatcher,
3337
}));
3438

3539
const originalFetch = globalThis.fetch;
@@ -39,6 +43,7 @@ afterEach(() => {
3943
setDefaultAutoSelectFamily.mockReset();
4044
setDefaultResultOrder.mockReset();
4145
setGlobalDispatcher.mockReset();
46+
getGlobalDispatcher.mockReset().mockReturnValue(mockOriginalDispatcher);
4247
AgentCtor.mockClear();
4348
vi.unstubAllEnvs();
4449
vi.clearAllMocks();
@@ -168,19 +173,21 @@ describe("resolveTelegramFetch", () => {
168173
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
169174
});
170175

171-
it("does not replace global dispatcher when autoSelectFamily is false", async () => {
176+
it("restores original dispatcher when autoSelectFamily switches from true to false", async () => {
172177
globalThis.fetch = vi.fn(async () => ({})) as unknown as typeof fetch;
173178
resolveTelegramFetch(undefined, { network: { autoSelectFamily: true } });
174179
resolveTelegramFetch(undefined, { network: { autoSelectFamily: false } });
175180

176-
// Only the first call (true) should replace the dispatcher;
177-
// switching to false should leave the default dispatcher untouched.
178-
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
179-
expect(AgentCtor).toHaveBeenNthCalledWith(1, {
181+
// First call replaces with autoSelectFamily agent, second restores original.
182+
expect(setGlobalDispatcher).toHaveBeenCalledTimes(2);
183+
expect(AgentCtor).toHaveBeenCalledTimes(1);
184+
expect(AgentCtor).toHaveBeenCalledWith({
180185
connect: {
181186
autoSelectFamily: true,
182187
autoSelectFamilyAttemptTimeout: 300,
183188
},
184189
});
190+
// Second setGlobalDispatcher call should restore the original dispatcher.
191+
expect(setGlobalDispatcher).toHaveBeenNthCalledWith(2, mockOriginalDispatcher);
185192
});
186193
});

src/telegram/fetch.ts

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as dns from "node:dns";
22
import * as net from "node:net";
3-
import { Agent, setGlobalDispatcher } from "undici";
3+
import { Agent, getGlobalDispatcher, setGlobalDispatcher } from "undici";
4+
import type { Dispatcher } from "undici";
45
import type { TelegramNetworkConfig } from "../config/types.telegram.js";
56
import { resolveFetch } from "../infra/fetch.js";
67
import { createSubsystemLogger } from "../logging/subsystem.js";
@@ -12,6 +13,7 @@ import {
1213
let appliedAutoSelectFamily: boolean | null = null;
1314
let appliedDnsResultOrder: string | null = null;
1415
let appliedGlobalDispatcherAutoSelectFamily: boolean | null = null;
16+
let originalGlobalDispatcher: Dispatcher | null = null;
1517
const log = createSubsystemLogger("telegram/network");
1618

1719
// Node 22 workaround: enable autoSelectFamily to allow IPv4 fallback on broken IPv6 networks.
@@ -41,26 +43,39 @@ function applyTelegramNetworkWorkarounds(network?: TelegramNetworkConfig): void
4143
// inherit the same decision.
4244
// See: https://github.com/openclaw/openclaw/issues/25676
4345
//
44-
// IMPORTANT: Only replace the global dispatcher when autoSelectFamily is
45-
// explicitly *enabled*. Replacing it unconditionally (even with false)
46-
// swaps in a bare Agent that discards any configuration the original
47-
// default dispatcher carried, which can break other HTTP clients in the
48-
// same process (e.g. LLM provider fetches returning 403).
46+
// IMPORTANT: Replacing the global dispatcher unconditionally (even with
47+
// autoSelectFamily=false) swaps in a bare Agent that discards any config
48+
// the original default dispatcher carried, breaking other HTTP clients in
49+
// the same process (e.g. LLM provider fetches returning 403).
50+
//
51+
// When enabling (true): save the original dispatcher and replace it.
52+
// When disabling (false) after a previous enable: restore the original.
4953
if (
50-
autoSelectDecision.value === true &&
54+
autoSelectDecision.value !== null &&
5155
autoSelectDecision.value !== appliedGlobalDispatcherAutoSelectFamily
5256
) {
5357
try {
54-
setGlobalDispatcher(
55-
new Agent({
56-
connect: {
57-
autoSelectFamily: true,
58-
autoSelectFamilyAttemptTimeout: 300,
59-
},
60-
}),
61-
);
62-
appliedGlobalDispatcherAutoSelectFamily = true;
63-
log.info(`global undici dispatcher autoSelectFamily=true`);
58+
if (autoSelectDecision.value) {
59+
// Save the original dispatcher before first replacement.
60+
if (originalGlobalDispatcher === null) {
61+
originalGlobalDispatcher = getGlobalDispatcher();
62+
}
63+
setGlobalDispatcher(
64+
new Agent({
65+
connect: {
66+
autoSelectFamily: true,
67+
autoSelectFamilyAttemptTimeout: 300,
68+
},
69+
}),
70+
);
71+
appliedGlobalDispatcherAutoSelectFamily = true;
72+
log.info(`global undici dispatcher autoSelectFamily=true`);
73+
} else if (originalGlobalDispatcher !== null) {
74+
// Restore the original dispatcher so other HTTP clients are unaffected.
75+
setGlobalDispatcher(originalGlobalDispatcher);
76+
appliedGlobalDispatcherAutoSelectFamily = false;
77+
log.info("global undici dispatcher restored to original");
78+
}
6479
} catch {
6580
// ignore if setGlobalDispatcher is unavailable
6681
}
@@ -104,4 +119,5 @@ export function resetTelegramFetchStateForTests(): void {
104119
appliedAutoSelectFamily = null;
105120
appliedDnsResultOrder = null;
106121
appliedGlobalDispatcherAutoSelectFamily = null;
122+
originalGlobalDispatcher = null;
107123
}

0 commit comments

Comments
 (0)