Blog

OpenClaw "Headers Timeout Error": why your configured timeout is ignored

If OpenClaw fails with fetch failed | Headers Timeout Error and the request dies at a number you never configured, your timeout is not being ignored — a lower deadline you didn't set is winning. The HTTP client underneath OpenClaw (undici, built into Node.js) waits a fixed amount of time for the model provider to send back response headers, and if the model hasn't started replying by then it closes the socket. The number tells you which layer killed it: exactly 60 s is the SSRF-guarded dispatcher dropping your timeout (openclaw#70829, since fixed — upgrade); exactly 300 s is undici's global default; anything else is your own setting. Read the number before you change anything.

What you'll see
# the surfaced error — note that it names no timeout at all:
fetch failed | Headers Timeout Error

# the real cause, one level down on the error object:
name: TypeError, message: "fetch failed", cause.code: UND_ERR_HEADERS_TIMEOUT

# the fingerprint: it dies at the SAME wall-clock number every time,
# and that number is not the one you configured.

Which layer timed out — read the number

This is one failure class with three possible deadlines. Whichever is lowest fires first and wins, so the elapsed time at failure identifies the layer:

Dies at… What actually fired What to do
Exactly 60 s undici's 60000 ms default, applied because the SSRF-guarded dispatcher didn't receive your timeoutMs (#70829) Upgrade OpenClaw — fixed in PR #70831
Exactly ~300 s undici's global-dispatcher default headersTimeout of 300000 ms (#69390) Make the model reply sooner; raise the ceiling only as a last resort
Your configured value OpenClaw's own agent deadline (agents.defaults.timeoutSeconds) — the layer that should be binding Raise it, or reduce how long the model takes to start

Time one failing request before you change any config. If the elapsed time is suspiciously round and identical across attempts, you are looking at a default, not at your setting.

Why your timeoutSeconds doesn't apply

OpenClaw sets an abort deadline for the agent turn, but the HTTP request underneath has its own, and the two are configured independently. In openclaw#70829 (2026-04-23), local LLM requests configured with timeoutSeconds: 900 — fifteen minutes — were dying at exactly 60 seconds.

The cause was specific and worth understanding, because it explains why the setting looked broken. To protect against SSRF, OpenClaw doesn't send provider requests on the shared global dispatcher; fetchWithSsrFGuard() builds an isolated dispatcher per request (createHttp1Agent(), createHttp1EnvHttpProxyAgent(), createHttp1ProxyAgent(), or createPinnedDispatcher() in undici-runtime.ts). None of those accepted or applied the timeoutMs parameter, so each one fell back to undici's default of 60000 ms for both headersTimeout and bodyTimeout. The socket was torn down a full fourteen minutes before the correctly-configured AbortSignal would have fired.

So the config was right and the request still died. That gap was closed by threading timeoutMs through to dispatcher creation (PR #70831) — which is why, if you are seeing the 60-second variant, upgrading is the fix and tuning is a detour. The same "configured timeout isn't respected" complaint appears in #60636 and #46049.

Why the error says "fetch failed" instead of "timeout"

This is the part that costs people the most time, and it's worth stating plainly: the message lies, and the cause tells the truth. A headers timeout arrives as a TypeError whose message is just fetch failed. Nothing in that string mentions a deadline, so it reads like a network or DNS problem and people go and check connectivity that was never broken. The actual information is one level down, on cause.code:

try {
  await fetch(providerUrl, { signal: AbortSignal.timeout(twentyMinutes) })
} catch (err) {
  // err.message      -> "fetch failed"                 (says nothing)
  // err.name         -> "TypeError"  (NOT "AbortError")
  // err.cause?.code  -> "UND_ERR_HEADERS_TIMEOUT"      (the real answer)
}

There's a second trap hiding in that snippet. Because the error's name is TypeError and not AbortError, any error handling that classifies timeouts by checking the error name will not recognise this as a timeout — it falls through to the generic branch and gets recorded as an availability or connectivity failure. The system then reports that the provider is unreachable when the provider was fine and simply slow. OpenClaw has an open request to surface better context on exactly this class of failure (#30262).

We know the shape of that trap because we walked into it. Lobsterland's own Hermes gateway adapter called a long-running agent endpoint with a 20-minute budget set via AbortController, and runs failed at almost exactly 300 seconds with a message claiming the endpoint was unreachable — while the agent was alive and finishing the work. Node's global undici dispatcher defaults headersTimeout to 300000 ms, and that fired first, silently capping a 20-minute budget at five. On one instance, 86 of 500 runs failed; 70 of those were this error, and 54 of them at exactly 300 seconds. Reproduced against a server that accepts a connection but never sends headers, it returns elapsed_sec=301.1, name=TypeError, msg=fetch failed, cause=UND_ERR_HEADERS_TIMEOUT — the same fingerprint, every time.

How to fix it

  1. Time the failure, then upgrade if it's 60 s. If requests die at exactly 60 seconds, you are on a build from before the #70829 dispatcher fix. Upgrading applies your configured timeout for real. Do this before touching any other setting — everything below is a workaround for a problem you may no longer have.
  2. Make sure OpenClaw's own deadline is generous enough to be the binding one. Raise agents.defaults.timeoutSeconds to comfortably exceed your model's worst-case time-to-first-token. This is the setting that should govern the request; the point of the upgrade above is to let it.
  3. Only if you're still capped at exactly ~300 s: raise undici's own default. The global-dispatcher default is set at the Node level, so overriding it means a preload script that replaces the global dispatcher before OpenClaw starts, wired in via NODE_OPTIONS --require. Understand the cost before you do it: it is a process-wide monkey-patch that affects every outbound HTTP request the runtime makes, and a stuck connection that would have been dropped in five minutes will now hang for as long as you allow. Treat it as a last resort, not a default hardening step.
  4. Attack the real problem: time-to-first-token. This error fires because nothing came back at all within the window, which on a slow local model usually means it is still loading weights or prefilling a long prompt. Keep the model resident instead of cold-loading it per request, shrink the context you send, or move to a smaller quantisation or a machine that can serve it. #69390 is a CPU-only qwen3.5:9b on a small VPS — no timeout value makes that combination fast. If you're setting up a local model, our OpenClaw local Ollama setup guide covers keeping it warm.
  5. Confirm it's actually this and not a per-provider stream failure. A headers timeout dies before the first token. If your run starts streaming and then stalls, that's a different failure — see the Gemini stream timeout guide.

If you're writing your own client against a gateway

One transport lesson from fixing our own adapter, since it's the part people get wrong twice. Setting an AbortController deadline on fetch() does not mean your deadline is the only one — undici's header deadline still applies underneath, and if it's lower, yours never fires.

The tempting fix is to build an undici Agent with a longer headersTimeout and pass it as a dispatcher. We chose not to: passing the npm undici package's Agent into Node's built-in fetch ties you to Node's internal copy of undici, so a runtime upgrade can silently change behaviour under you. We moved the transport to node:http/node:https instead, which imposes no response deadline of its own, leaving the application's own timer as the single deadline — and made timeout classification structural (our own timer raises the timeout) rather than string-matching on undici error codes. Shipped 2026-07-20. If you only take one thing from this section: make sure exactly one layer owns the deadline, and make sure you know which.

Stop babysitting your OpenClaw box

Fix it once — or stop fixing it for good.

Apply the checklist above and keep self-hosting, or skip the maintenance entirely: run your OpenClaw on managed hosting from $6.90/mo, starting with a 7-day free trial. We handle the stale locks, gateway restarts, version upgrades, and uptime — and you can import your existing instance in a couple of minutes. Cancel anytime.

Managed hosting — from $6.90/mo Your own hosted OpenClaw instance with automatic restarts and version upgrades. Starts with a 7-day free trial — import your current setup, keep your channels, cancel anytime.
$199 managed setup — optional Prefer we do it for you? One workspace configured end-to-end: first-run config, one 30-minute onboarding/debug session, and a 7-day follow-up. Limited weekly slots.
  • Managed hosting handles stale .jsonl.lock files, gateway restarts, and version upgrades for you
  • Import your existing OpenClaw setup in minutes — keep your channels and configuration
  • The optional $199 setup is scoped: no custom development, enterprise/SRE support, or unsupported self-hosting repair

If you would rather compare options first, review OpenClaw cloud hosting or see the best OpenClaw hosting options before deciding.

OpenClaw import first screen in OpenClaw Setup dashboard (light theme) OpenClaw import first screen in OpenClaw Setup dashboard (dark theme)
1) Paste import payload
OpenClaw import completed screen in OpenClaw Setup dashboard (light theme) OpenClaw import completed screen in OpenClaw Setup dashboard (dark theme)
2) Review and launch
What managed hosting does and doesn't change here

Being honest about this one, because the failure is partly upstream. Managed hosting does not remove the undici header deadline — it is Node-level behaviour, and Lobsterland ships no undici timeout patch to hosted instances (the only NODE_OPTIONS we set is the heap size). What it does change is the two things around it: instances run a pinned, tested OpenClaw build, so the #70829 dispatcher fix is simply present rather than a version you have to chase after losing an afternoon to it, and generated instance config sets agents.defaults.timeoutSeconds to 1800 seconds (30 minutes) by default, so the binding deadline is a deliberate one. And because hosted instances call model providers over the network with your own keys rather than running a CPU-bound local model, the five-minute-to-first-token shape that triggers this barely comes up. If you want the runtime, version, and config lifecycle handled for you, that's managed OpenClaw hosting from Lobsterland. If you're committed to a slow local model on your own hardware, the header deadline is an upstream constraint and no host removes it for you.

Frequently asked questions

What does "Headers Timeout Error" mean in OpenClaw?

The HTTP client gave up waiting for the model provider to send back response headers — the first byte of the reply — and closed the socket. It is not OpenClaw's own agent timeout. The error comes from undici, the HTTP client built into Node.js, and surfaces as fetch failed | Headers Timeout Error with the underlying code UND_ERR_HEADERS_TIMEOUT. The request never got a reply started, so nothing was generated. It shows up most often against slow local models, because the model spends minutes loading or prefilling before it emits its first token.

Why is my OpenClaw timeoutSeconds setting ignored?

Because a lower timeout you didn't set is winning. Your timeoutSeconds value controls OpenClaw's own abort deadline, but the HTTP layer underneath has its own independent deadline, and whichever fires first kills the request. In openclaw#70829 (2026-04-23) requests configured with timeoutSeconds: 900 died at exactly 60 seconds, because the isolated dispatchers created by fetchWithSsrFGuard() for SSRF protection did not accept or apply the timeoutMs parameter and fell back to undici's 60000 ms default for both headersTimeout and bodyTimeout. That was fixed in PR #70831, so on a current build the first thing to do is upgrade, not tune.

Why does the error say "fetch failed" instead of "timeout"?

Because the message describes the symptom, not the cause — and it sends people to debug the network instead of the deadline. The thrown object is a TypeError whose message is fetch failed; the real information is one level down, in cause.code = UND_ERR_HEADERS_TIMEOUT. Its name is also not AbortError, so timeout-aware error handling that checks the error name won't recognise it as a timeout and will record it as a connectivity or availability failure. Always read cause.code before you conclude a provider is unreachable.

Why does my local Ollama model fail at exactly 5 minutes?

Five minutes is 300000 ms, the default headersTimeout on Node's global undici dispatcher. If a request dies at almost exactly 300 seconds, that default is the deadline that fired — not anything you configured. openclaw#69390 reports this on 2026.4.15 against a CPU-only Ollama qwen3.5:9b, failing at about five minutes before the first response token even though OpenClaw's own timeouts had already been raised. A CPU-only model that needs longer than five minutes to produce its first token will hit this every time, so the durable fix is to make the model start answering sooner, not only to raise the ceiling.

Does managed hosting remove the undici timeout?

No, and it would be dishonest to say otherwise. The undici header deadline is a Node.js and OpenClaw-level behaviour, and Lobsterland does not ship an undici timeout patch to hosted instances — the only NODE_OPTIONS we set on an instance is the heap size. What managed hosting does change is the two things around it: instances run a pinned, tested OpenClaw build, so the dispatcher fix from PR #70831 is present instead of being a version you have to chase, and generated instance config sets agents.defaults.timeoutSeconds to 1800 seconds (30 minutes) by default. If you run a slow local model on your own hardware, the header deadline is an upstream constraint and no host removes it for you.

Sources

  • openclaw#70829 — propagate timeoutMs to guarded dispatchers (local LLM 60 s timeout), 2026-04-23, fixed via PR #70831.
  • openclaw#69390 — Ollama local model hits ~5-minute "Headers Timeout Error" on 2026.4.15 despite increased OpenClaw timeouts, 2026-04-20.
  • openclaw#60636agents.defaults.timeoutSeconds not respected for local OpenAI-compatible providers.
  • openclaw#46049 — LLM request timeout ignores configured timeout settings.
  • openclaw#30262 — surface actionable error context on LLM provider timeouts instead of a generic message.
  • nodejs/undici#1272 — the upstream "Headers Timeout Error" (UND_ERR_HEADERS_TIMEOUT).

Related fixes

Cookie preferences