Skip to content

fix: Windows Node.js globalThis.fetch undefined 启动报错#833

Open
deep-innovator wants to merge 1 commit intojackwener:mainfrom
deep-innovator:main
Open

fix: Windows Node.js globalThis.fetch undefined 启动报错#833
deep-innovator wants to merge 1 commit intojackwener:mainfrom
deep-innovator:main

Conversation

@deep-innovator
Copy link
Copy Markdown

在 Windows 环境下的 Node.js 中,不会自动将 fetch 挂载到 globalThis, 导致启动 opencli daemon 时报错:

TypeError: Cannot read properties of undefined (reading 'bind')

问题原因

const nativeFetch = globalThis.fetch.bind(globalThis); Windows 上 globalThis.fetch 为 undefined

修复方案

替换为兼容异步加载 Node 内置 fetch 的写法:

const nativeFetch = async () => {
const { fetch } = await import('node:util');
return fetch;
};

验证环境

  • Node.js v20.20.2
  • Windows 10
  • 已正常启动 opencli daemon 并使用

Description

Related issue:

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 🌐 New site adapter
  • 📝 Documentation
  • ♻️ Refactor
  • 🔧 CI / build / tooling

Checklist

  • I ran the checks relevant to this PR
  • I updated tests or docs if needed
  • I included output or screenshots when useful

Documentation (if adding/modifying an adapter)

  • Added doc page under docs/adapters/ (if new adapter)
  • Updated docs/adapters/index.md table (if new adapter)
  • Updated sidebar in docs/.vitepress/config.mts (if new adapter)
  • Updated README.md / README.zh-CN.md when command discoverability changed
  • Used positional args for the command's primary subject unless a named flag is clearly better
  • Normalized expected adapter failures to CliError subclasses instead of raw Error

Screenshots / Output

在 Windows 环境下的 Node.js 中,不会自动将 fetch 挂载到 globalThis,
导致启动 opencli daemon 时报错:

TypeError: Cannot read properties of undefined (reading 'bind')

### 问题原因
const nativeFetch = globalThis.fetch.bind(globalThis);
Windows 上 globalThis.fetch 为 undefined

### 修复方案
替换为兼容异步加载 Node 内置 fetch 的写法:

const nativeFetch = async () => {
  const { fetch } = await import('node:util');
  return fetch;
};

### 验证环境
- Node.js v20.20.2
- Windows 10
- 已正常启动 opencli daemon 并使用
Copy link
Copy Markdown
Contributor

@Astro-Han Astro-Han left a comment

Choose a reason for hiding this comment

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

This patch breaks the no-proxy path.

nativeFetch used to be the fetch implementation itself:

const nativeFetch = globalThis.fetch.bind(globalThis);

That matches the later call site:

return nativeFetch(input, init);

After this change, nativeFetch is no longer the fetch implementation. It is an async function that tries to load one:

const nativeFetch = async () => {
  const { fetch } = await import('node:util');
  return fetch;
};

Two issues here:

  1. node:util does not export fetch on current Node versions, so this resolves to undefined.
  2. fetchWithNodeNetwork() still calls nativeFetch(input, init) as if nativeFetch were the request function. It now returns a fetch function, or undefined, instead of returning a Response.

So the no-proxy branch no longer returns a Response. This changes the failure mode, but does not fix it.

The fix needs to keep the original contract intact: nativeFetch still needs to behave like (input, init) => Promise<Response>, while also handling environments where globalThis.fetch is missing at module load time.

@deep-innovator
Copy link
Copy Markdown
Author

deep-innovator commented Apr 6, 2026 via email

@Astro-Han
Copy link
Copy Markdown
Contributor

Thanks, that matches what I’m seeing. The Windows issue looks real, but the current fallback changes what nativeFetch is.

I think the least invasive fix is to keep nativeFetch as the request function itself, and only change where it comes from:

const nativeFetch: typeof globalThis.fetch = globalThis.fetch
  ? globalThis.fetch.bind(globalThis)
  : ((input: RequestInfo | URL, init?: RequestInit) => (
      undiciFetch(
        input as Parameters<typeof undiciFetch>[0],
        init as Parameters<typeof undiciFetch>[1],
      ) as unknown as Promise<Response>
    ));

That keeps this call site valid:

return nativeFetch(input, init);

So the no-proxy path still behaves the same:

  • use globalThis.fetch when it exists
  • fall back to undiciFetch when it does not

I would avoid a lazy lookup like (globalThis.fetch ?? undiciFetch)(...) inside the wrapper, because after installNodeNetwork() runs, globalThis.fetch points back to this wrapper.

If helpful, I can also sketch the regression test for the case where globalThis.fetch is missing at module load time.

@Astro-Han
Copy link
Copy Markdown
Contributor

If you want to keep this in the current PR, that makes sense.

If it saves time, I can also send a small PR with the fallback change and a regression test for the case where globalThis.fetch is missing at module load time.

Either way works for me.

@deep-innovator
Copy link
Copy Markdown
Author

deep-innovator commented Apr 6, 2026 via email

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants