Skip to content

fix(core): disable Node fetch timeouts for provider requests - #55566

Open
Hona wants to merge 1 commit into
anomalyco:devfrom
Hona:node-provider-timeouts
Open

Hona wants to merge 1 commit into
anomalyco:devfrom
Hona:node-provider-timeouts

Conversation

@Hona

@Hona Hona commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Fixes #26602

On Node, a provider that sends nothing for 5 minutes is disconnected with UND_ERR_HEADERS_TIMEOUT, even with headerTimeout: false. Node's built-in fetch applies undici's default 300s headersTimeout and bodyTimeout. Bun has the same deadline, and Core already turns it off with timeout: false. This PR turns off the Node deadline in the same way.

  • Who is affected: the Node CLI (opencode2-node) and Core embedded in a Node process. Desktop 2.0.x starts the bundled Bun CLI (opencode-cli serve --service), the same service the TUI uses, so it already behaves correctly.
  • What still limits a request: the provider settings headerTimeout (default 300s), chunkTimeout (default 900s), and timeout (no default), on every runtime.
sequenceDiagram
  participant S as opencode service (Node)
  participant P as Provider (Ollama, router, ...)
  S->>P: POST /v1/chat/completions
  Note over P: reads a long prompt, sends no bytes
  Note over S: 300s: undici headersTimeout fires
  S--xP: socket closed: UND_ERR_HEADERS_TIMEOUT
  S->>P: retry about 2s later, same result (up to 10 retries)
Loading

Change

Both provider request paths (native routes through RequestExecutor, AI SDK routes through aisdk.ts) now get their fetch from a runtime-conditioned import, like #sqlite and #pty:

flowchart LR
  N[Native routes<br/>RequestExecutor] --> R{"#runtime-fetch"}
  A[AI SDK routes<br/>aisdk.ts] --> R
  R -->|bun| B["ambient fetch + timeout: false<br/>(unchanged behavior)"]
  R -->|node| U["undici fetch + EnvHttpProxyAgent<br/>headersTimeout: 0, bodyTimeout: 0"]
  R -->|workerd| W[platform fetch]
Loading
// packages/core/src/effect/runtime-fetch.node.ts
const dispatcher = new EnvHttpProxyAgent({ headersTimeout: 0, bodyTimeout: 0 })

export function withoutRuntimeTimeouts(runtime: Fetch): Fetch {
  if (runtime !== globalThis.fetch) return runtime
  return Object.assign(
    (input: Parameters<Fetch>[0], init?: RequestInit) =>
      input instanceof Request || init?.body instanceof FormData
        ? runtime(input, init)
        : (fetch(input, { ...init, dispatcher } as Parameters<typeof fetch>[1]) as unknown as Promise<Response>),
    { preconnect: runtime.preconnect },
  )
}
  • Undici's fetch, not the built-in one: Node 24's built-in fetch rejects an agent from undici 8 (UND_ERR_INVALID_ARG), so the agent is paired with undici's own fetch.
  • FormData and Request keep the built-in fetch: undici's fetch does not recognize the built-in classes. For example, it sends a built-in FormData as the text [object FormData], which would break image and transcription uploads.
  • Proxy variables: EnvHttpProxyAgent honors HTTP_PROXY, HTTPS_PROXY, and NO_PROXY, like Bun's fetch and Core's WebSocket transport on Node.
  • Injected fetch stays in charge: a fetch from provider settings, a host, or the test network guard is never replaced on Node. On Bun it still receives timeout: false.
  • Bundles: Bun and workerd do not load undici. The Node CLI bundles it through a Vite alias. Without the alias, Vite (which runs under Bun, where undici is a builtin) leaves it external and the executable fails at startup with Cannot find package 'undici'.

Before and after

A real V2 server and an OpenAI-compatible provider that waits 330s before it sends response headers, with headerTimeout: false:

Service runtime Before After
Bun (Desktop 2.0.29, TUI) Answered after 330s Answered after 330s
Node 26.4 (opencode2-node) Disconnected after 305s: UND_ERR_HEADERS_TIMEOUT: Headers Timeout Error, then retried Answered after 330s

With headerTimeout: 20000 and a 60s provider delay, patched Node still ends the request after 20s with Timed out waiting for response headers.

Relation to #54793

#54793 by @Yedigaryan found the same cause. This PR differs in three places:

  • Undici is loaded only under the node condition, so it stays out of the Bun and workerd graphs (the review note on fix(core): lift undici's 300s timeouts on Node #54793).
  • FormData and Request inputs keep the built-in fetch.
  • The Node CLI bundle includes undici; otherwise the executable cannot start.

Added by the opencode bot (AI-generated).

Public API: On Node, provider requests now honour HTTP_PROXY/HTTPS_PROXY/NO_PROXY, matching Bun (behaviour change, no API surface change)

Copilot AI balanced review requested due to automatic review settings October 14, 2026 23:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good. I reproduced the problem on Node 24: against a server that waits 310s before sending headers, the built-in fetch fails at 300s with UND_ERR_HEADERS_TIMEOUT, and the new withoutRuntimeTimeouts fetch returns 200 at 310s. The --bundle-only Node CLI build bundles undici and opencode2-node starts. The core tests closest to the change (aisdk, aisdk-native, request hooks, app-node-platform) and typecheck for core and cli all pass.

Public API: no SDK, HTTP, plugin, config or CLI changes. One behaviour change on Node: provider requests now honour HTTP_PROXY, HTTPS_PROXY and NO_PROXY through EnvHttpProxyAgent. The built-in fetch only did that with NODE_USE_ENV_PROXY, so Node users who have those variables set will now send provider traffic through the proxy, as Bun already does.

I have no blocking concerns.

This branch has not been deployed

No deployments
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.

Desktop hits 5-minute Headers Timeout Error with slow local providers

2 participants