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.
# 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
- 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.
- Make sure OpenClaw's own deadline is generous enough to be the binding one.
Raise
agents.defaults.timeoutSecondsto 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. - 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. - 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:9bon 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. - 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 handles stale
.jsonl.lockfiles, 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.
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
timeoutMsto 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#60636 —
agents.defaults.timeoutSecondsnot 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
- OpenClaw Gemini stream timeout — the run starts streaming and then stalls, rather than never starting.
- OpenClaw gateway timeout after 10000ms — a gateway WebSocket timeout on device pairing; same word, different subsystem.
- OpenClaw cron run timeout — the scheduler's own deadline, not the HTTP layer's.
- OpenClaw session file locked timeout — a lock wait that also reports as a timeout but has nothing to do with HTTP.