Skip to content

Pin fetch to bundled undici for undici higher versions compatibility#4238

Merged
yiliang114 merged 4 commits into
QwenLM:mainfrom
ideal:fix-fetch-failed-error
May 20, 2026
Merged

Pin fetch to bundled undici for undici higher versions compatibility#4238
yiliang114 merged 4 commits into
QwenLM:mainfrom
ideal:fix-fetch-failed-error

Conversation

@ideal

@ideal ideal commented May 17, 2026

Copy link
Copy Markdown
Contributor

Node.js 26 (on ArchLinux installed by sudo pacman -S nodejs) bundles undici 8.x, which differs from the project's undici 6.x. Using Node's built-in fetch mixed with ProxyAgent/Client from the bundled undici causes handler-interface mismatches (e.g. 'InvalidArgumentError: invalid onError method').

This PR should fix #4035 #3914 #4274

Summary

  • What changed: pin fetch to bundled undici.
  • Why it changed: for undici higher versions compatibility
  • Reviewer focus: calling to LLM APIs.

Validation

  • Commands run:
    npm install
    npm run build
    sudo npm link
    OPENAI_BASE_URL=https://api.deepseek.com OPENAI_MODEL=deepseek-v4-flash OPENAI_API_KEY=sk-xxxx qwen
    # Ask questions in qwen
  • Prompts / inputs used: who are you?
  • Expected result: 我是 Qwen Code,由阿里云开发的一个交互式 CLI 智能体,专注于软件工程任务。我可以帮你编写、修改、调试代码,运行测试,管理项目,以及处理各种开发相关的工作。有什么我可以帮你的吗?
  • Observed result: 我是 Qwen Code,由阿里云开发的一个交互式 CLI 智能体,专注于软件工程任务。我可以帮你编写、修改、调试代码,运行测试,管理项目,以及处理各种开发相关的工作。有什么我可以帮你的吗?
  • Quickest reviewer verification path: Using Node.js v26.1.0,without patch,it will show error: [API Error: Connection error. (cause: fetch failed)]
  • Evidence (output, logs, screenshots, video, JSON, before/after, etc.):

Before:

截屏2026-05-18 16 13 11
2026-05-12T10:24:48.153Z [ERROR] [ERROR_REPORT] Error when talking to API [Turn.run-sendMessageStream] {
  "error": {
    "message": "Connection error.",
    "stack": "Error: Connection error.\n    at OpenAI.makeRequest (file:///usr/lib/node_modules/@qwen-code/qwen-code/dist/cli.js:156446:17)\n    at async file:///usr/lib/node_modules/@qwen-code/qwen-code/dist/cli.js:157771:29\n    at async ContentGenerationPipeline.executeWithErrorHandling (file:///usr/lib/node_modules/@qwen-code/qwen-code/dist/cli.js:157995:26)\n    at async LoggingContentGenerator.generateContentStream (file:///usr/lib/node_modules/@qwen-code/qwen-code/dist/cli.js:147297:21)\n    at async retryWithBackoff (file:///usr/lib/node_modules/@qwen-code/qwen-code/dist/cli.js:173519:22)\n    at async GeminiChat.makeApiCallAndProcessStream (file:///usr/lib/node_modules/@qwen-code/qwen-code/dist/cli.js:179294:33)\n    at async file:///usr/lib/node_modules/@qwen-code/qwen-code/dist/cli.js:179067:33\n    at async Turn.run (file:///usr/lib/node_modules/@qwen-code/qwen-code/dist/cli.js:177019:28)\n    at async GeminiClient.sendMessageStream (file:///usr/lib/node_modules/@qwen-code/qwen-code/dist/cli.js:237801:26)\n    at async file:///usr/lib/node_modules/@qwen-code/qwen-code/dist/cli.js:556004:26"
  },

Scope / Risk

  • Main risk or tradeoff: none.
  • Not covered / not validated: non OpenAI compatible LLM APIs.
  • Breaking changes / migration notes: none.

Testing Matrix

🍏 🪟 🐧
npm run ⚠️
npx ⚠️ ⚠️ ⚠️
Docker ⚠️ ⚠️ ⚠️
Podman ⚠️ N/A N/A
Seatbelt ⚠️ N/A N/A

Testing matrix notes:

  • Tested on macOS and Linux with Node.js v26.0.0.

Linked Issues / Bugs

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ REQUEST_CHANGES — Two Critical issues found:

[Critical] DashScopeOpenAICompatibleProvider.buildClient() (dashscope.ts:98) overrides the parent method but is missing the fetch: undiciFetch pin added in this PR.
DashScope is the only subclass that overrides buildClient(). Users with proxy configured + DashScope will still encounter the invalid onError method error this PR aims to fix.
Fix: add fetch: undiciFetch as unknown as typeof fetch to the new OpenAI({...}) call in dashscope.ts:113 (matching the fix in default.ts:70).

[Critical] apiPreconnect.test.ts:57 uses vi.stubGlobal('fetch', mockFetch) which will not intercept the import { fetch } from 'undici' added by this PR.
17+ test cases will pass vacuously — mockFetch will never be called.
Fix: replace vi.stubGlobal('fetch', mockFetch) with vi.mock('undici', ...) to stub the module's fetch export.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Comment thread packages/core/src/core/openaiContentGenerator/provider/default.ts Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] 15/26 apiPreconnect tests are broken. The source now calls undiciFetch (imported from the undici module), but the test file still mocks globalThis.fetch via vi.stubGlobal('fetch', mockFetch). The mock never intercepts the actual call, so all assertions on mockFetch fail. Fix: mock the undici module's fetch export instead of the global.

[Critical] Fix is incomplete — dashscope.ts and anthropicContentGenerator.ts lack fetch pinning. dashscope.ts overrides buildClient() and constructs new OpenAI({…}) without fetch: undiciFetch, so proxy DashScope users still hit the invalid onError method crash. anthropicContentGenerator.ts passes a bundled-undici ProxyAgent dispatcher to the Anthropic SDK but doesn't pin fetch, creating the same mismatch. Add fetch: undiciFetch to both constructors, or centralize the pinning into buildRuntimeFetchOptions so all call sites are covered.

— glm-5.1 via Qwen Code /review

Comment thread packages/core/src/core/openaiContentGenerator/provider/default.ts Outdated
Comment thread packages/core/src/core/openaiContentGenerator/provider/default.ts Outdated
ideal added a commit to ideal/qwen-code that referenced this pull request May 18, 2026
…rsion mismatch

for review of QwenLM#4238

When a custom dispatcher (ProxyAgent) is passed, pin fetch to the bundled undici's implementation so both share the same undici version. Without this, Node's built-in fetch (e.g. undici v8) rejects a ProxyAgent from the bundled undici (e.g. v6) with "invalid onError method".
@ideal

ideal commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

@wenshao updated according to review comments.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Anthropic proxy bypassanthropicContentGenerator.ts:213 spreads ...runtimeOptions (containing { fetchOptions: { dispatcher }, fetch: undiciFetch }) into new Anthropic(). The Anthropic SDK only stores fetchfetchOptions is silently discarded. When the SDK calls fetch(url, req), req has no dispatcher, so undiciFetch runs without a proxy dispatcher. This is a regression for Anthropic proxy users. Fix: bind the dispatcher to the fetch function before passing it to the SDK.

[Critical] No test for fetch: undiciFetch pin in default.ts — The primary behavioral change (unconditional fetch pinning) has zero test coverage in default.test.ts. If the pin is accidentally removed, all tests still pass.

— mimo-v2.5-pro via Qwen Code /review

Comment thread packages/core/src/core/openaiContentGenerator/provider/default.ts Outdated
Comment thread packages/core/src/core/openaiContentGenerator/provider/default.ts Outdated
Comment thread packages/core/src/utils/runtimeFetchOptions.test.ts

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] packages/core/src/core/openaiContentGenerator/provider/default.test.ts — missing test assertion for fetch in buildClient()

buildClient() now passes fetch: undiciFetch to the OpenAI constructor, but the existing test at ~line 127 uses expect.objectContaining({ apiKey, baseURL, timeout, maxRetries, defaultHeaders }) which passes even if fetch is absent. If someone accidentally removes the fetch: undiciFetch line, no regression test would catch it.

Suggested fix: add an explicit assertion:

expect(OpenAI).toHaveBeenCalledWith(
  expect.objectContaining({ fetch: expect.any(Function) }),
);

— DeepSeek/deepseek-v4-pro via Qwen Code /review

wenshao
wenshao previously approved these changes May 18, 2026

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found. LGTM! ✅ — mimo-v2.5-pro via Qwen Code /review

@ideal

ideal commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

[Suggestion] packages/core/src/core/openaiContentGenerator/provider/default.test.ts — missing test assertion for fetch in buildClient()

buildClient() now passes fetch: undiciFetch to the OpenAI constructor, but the existing test at ~line 127 uses expect.objectContaining({ apiKey, baseURL, timeout, maxRetries, defaultHeaders }) which passes even if fetch is absent. If someone accidentally removes the fetch: undiciFetch line, no regression test would catch it.

Suggested fix: add an explicit assertion:

expect(OpenAI).toHaveBeenCalledWith(
  expect.objectContaining({ fetch: expect.any(Function) }),
);

— DeepSeek/deepseek-v4-pro via Qwen Code /review

But if no proxy is set here, there is no fetch or dispatcher paased.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Approve to Comment: CI failing (Test (windows-latest, Node 22.x)). No blocking issues found.

Suggestion (out of scope): gitUtils.tssetupGithubCommand.ts 存在相同的 undici 版本不匹配模式(将捆绑 undici v6 的 ProxyAgent/dispatcher 传递给 Node 内置的 fetch),但此 PR 未处理。可在后续 PR 中修复。

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@@ -596,7 +603,13 @@ function buildFetchOptionsWithDispatcher(
// 300s bodyTimeout. This is sufficient for all current model streaming responses.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Anthropic SDK 静默丢弃 fetchOptions.dispatcher

buildFetchOptionsWithDispatcher 为两个 SDK 都返回 { fetchOptions: { dispatcher }, fetch: undiciFetch },但 Anthropic SDK(@anthropic-ai/sdk)构造函数不支持 fetchOptions——它只存储 fetch。dispatcher 被 Anthropic 路径静默丢弃。

这不是此 PR 引入的问题(PR 之前 dispatcher 就已经被返回了),但 fetch: undiciFetch 的显式添加使这种不对称性更加明显——未来维护者可能会看到这段代码,并认为 Anthropic 的代理路径是正常的,而实际上并非如此。

Suggested change
// 300s bodyTimeout. This is sufficient for all current model streaming responses.
// Note: Anthropic SDK does not support constructor-level fetchOptions,
// so the dispatcher is only effective for OpenAI SDK calls.
return { fetchOptions: { dispatcher }, fetch: undiciFetch };

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@ideal

ideal commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

@wenshao Do i need to modify gitUtils.ts and setupGithubCommand.ts ?

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No review findings. Downgraded from Approve to Comment: CI failing: Test (windows-latest, Node 22.x). — gpt-5.5 via Qwen Code /review

@ideal

ideal commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

The Windows CI Test is experiencing process spawn timeouts, which is not caused by this PR.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No review findings. Downgraded from Approve to Comment: CI failing: Test (windows-latest, Node 22.x). — qwen-latest-series-invite-beta-v28 via Qwen Code /review

@wenshao

wenshao commented May 19, 2026

Copy link
Copy Markdown
Collaborator

Windows CI 失败分析

失败的测试: packages/cli/src/serve/routes/workspaceFileRead.test.tsGET /glob > scopes glob matches to cwd

AssertionError: expected [ 'sub\inside.ts' ] to deeply equal [ 'sub/inside.ts' ]

根因: Windows 路径分隔符问题。workspaceRelative() 函数(workspaceFileRead.ts:504)已有处理 Windows 分隔符的逻辑:

return path.sep === '/' ? rel : rel.split(path.sep).join('/');

但在 Windows 上 glob 库返回的路径可能混合使用 /\ 分隔符,path.relative() 的输出中残留了反斜杠,导致最终 API 响应里出现了 sub\inside.ts 而非预期的 sub/inside.ts

这个失败跟本 PR 的改动无关 — PR 修改的是 undici/fetch 兼容性,而失败的测试是一个完全不相关的 workspace glob 路由。这是 main 分支上已有的 Windows 兼容性问题。

建议修复:workspaceFileRead.tsworkspaceRelative 函数中,将分隔符替换改为同时处理 /\

// 改前
return path.sep === '/' ? rel : rel.split(path.sep).join('/');

// 改后
return rel.split(/[\\/]/).join('/');

这样无论 path.relative 返回哪种分隔符都能正确归一化为 POSIX 风格。

@ideal

ideal commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

@wenshao it is better to fix Windows CI problem of workspaceFileRead.ts in anther PR ?

@yiliang114

Copy link
Copy Markdown
Collaborator

I took another pass on the latest diff. The current approach looks like the right direction to me: keeping undici@6 for VSCode/Node 18 compatibility while making the fetch implementation and dispatcher come from the same bundled undici instance should address the Node 26 mismatch without raising the VSCode support floor.

A few small follow-ups before merge:

  1. Could you update the PR description or add explicit references to the canonical tracking issues, especially fetch failed on DashScope-intl endpoint: undici dispatcher in DashScopeContentGenerator.buildClient() incompatible with dashscope-intl.aliyuncs.com #4035 / API connected, no errors but then fail to fetch #3914 and the duplicate qwen-code 0.15.11 fails with Connection error. (cause: fetch failed) on Node.js 26 unless fetchOptions.dispatcher is removed #4274? If this PR is intended to close them, putting the closing keywords in the PR description would make the release traceability clearer.
  2. Could you also briefly confirm the VSCode ACP + proxy path? The VSCode companion forwards VSCode http.proxy as --proxy, so that path should exercise this runtime fetch/dispatcher logic as well.
  3. The similar ProxyAgent + native fetch pattern in gitUtils.ts / setupGithubCommand.ts was raised earlier by @wenshao. I agree it can be handled as a follow-up and should not block this PR, but it would be useful to track it separately.

Assuming the Windows CI rerun passes or is confirmed unrelated, I don't see any remaining code-level blocker in this PR.

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM

@ideal

ideal commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

@yiliang114 1. updated description to metion about #4035 #3914 #4274
2. I will check that path.
3. I will track gitUtils.ts / setupGithubCommand.ts in another PR.

// breaks dispatcher handler-interface checks (`invalid onError method`).
// The no-proxy branch above intentionally skips this so the runtime's
// built-in fetch continues to be used when no dispatcher is involved.
return { fetchOptions: { dispatcher }, fetch: undiciFetch };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Missing success-path debug log. buildFetchOptionsWithDispatcher logs failures extensively (debugLogger.warn + console.error), but when dispatcher creation succeeds and fetch is pinned to undiciFetch, no log is emitted. When debugging proxy issues in production, there's no way to confirm the fetch override was injected without reading source code.

Consider adding a debug log before the return:

Suggested change
return { fetchOptions: { dispatcher }, fetch: undiciFetch };
debugLogger.debug(
`Proxy dispatcher created for ${extractHostnameFromProxyUrl(proxyUrl)}, fetch pinned to bundled undici`,
);
return { fetchOptions: { dispatcher }, fetch: undiciFetch };

— qwen-latest-series-invite-beta-v34 via Qwen Code /review

@wenshao

wenshao commented May 20, 2026

Copy link
Copy Markdown
Collaborator

看了一下 Windows CI(attempt #5)的两个失败,都跟本 PR(pin undici fetch)无关,是 Windows 平台 pre-existing 的问题:

  1. packages/cli/src/serve/routes/workspaceFileRead.test.ts > GET /glob > scopes glob matches to cwd — 期望 ['sub/inside.ts'],Windows 实际返回 ['sub\\inside.ts'],路径分隔符硬编码问题。
  2. packages/cli/src/ui/components/InputPrompt.test.tsx > prompt suggestions > accepts and submits the prompt suggestion on Enter when the buffer is empty — Windows runner 上 ink 7 + React 19.2 mount 超过 700ms,suggestion 还没渲染 Enter 就来了,是已知 flaky test。

建议先把最新的 origin/main rebase/merge 进来再跑一次 CI,看主干上是否已经修过这两个 case;如果还在,就跟本 PR 解耦另开 PR 修。辛苦了 🙏

ideal added 4 commits May 20, 2026 21:10
Node.js 26 bundles undici 8.x, which differs from the project's undici 6.x.
Using Node's built-in fetch mixed with ProxyAgent/Client from the bundled
undici causes handler-interface mismatches (e.g. 'invalid onError method').
…rsion mismatch

for review of QwenLM#4238

When a custom dispatcher (ProxyAgent) is passed, pin fetch to the bundled undici's implementation so both share the same undici version. Without this, Node's built-in fetch (e.g. undici v8) rejects a ProxyAgent from the bundled undici (e.g. v6) with "invalid onError method".
@ideal ideal force-pushed the fix-fetch-failed-error branch from e7acb11 to fa5f896 Compare May 20, 2026 13:11
@ideal

ideal commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

rebased on origin/main.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found. LGTM! ✅ — qwen-latest-series-invite-beta-v34 via Qwen Code /review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants