Summary
Setting agents.defaults.heartbeat.every to a duration greater than ~24.85 days (Node.js's signed-32-bit setTimeout cap of 2,147,483,647 ms) causes the heartbeat scheduler to fire in a tight loop and eventually crashes the gateway with OpenClaw exited with code 1. The container wrapper does not auto-respawn the gateway after exit. The CLI silently falls back to embedded mode on the next invocation.
Reproduction
- In
openclaw.json, set:
{ "agents": { "defaults": { "heartbeat": { "every": "365d" } } } }
(or any value that resolves to >2,147,483,647 ms — i.e. anything beyond ~24d 20h)
- Restart the gateway and watch container logs.
Observed behaviour
Container logs flood with thousands of lines like:
(node:41) TimeoutOverflowWarning: 23111245866 does not fit into a 32-bit signed integer.
Timeout duration was set to 1.
Eventually the gateway exits:
[22:29:40] WARN: OpenClaw exited with code 1
After this, port 18789 is not listening; subsequent openclaw agent invocations silently fall back to embedded mode (see related issue on silent embedded fallback). docker exec ... ps -ef shows no gateway process; only the proxy node server.mjs remains.
Root cause
Per Node.js docs, setTimeout(fn, delay) clamps delay > 2147483647 to 1 ms. The heartbeat scheduler appears to compute "next fire = now + every" and pass it directly to setTimeout, so the very-large delay gets truncated to 1 ms. The function then runs immediately, recomputes, and re-arms — a tight loop that ultimately exhausts something (event loop / promise queue / heap) and the process dies.
Expected behaviour
At least one of:
- Reject the config with a clear error during
loadConfig if the resolved ms exceeds Node's setTimeout limit.
- Clamp internally to
2^31-1 ms with a warning.
- Use a recursive long-timer pattern (re-arm every 24d until the cumulative target is reached).
Additionally, the wrapper / supervisor should auto-respawn the gateway after exit code 1 instead of leaving the proxy alive but the gateway dead.
Workaround
Use a value safely under the cap: "every": "24d" (≈ 2,073,600,000 ms) is safe and still effectively "never" for a "durably disabled" heartbeat.
Environment
OpenClaw 2026.4.12 (1c0672b), running in container ghcr.io/hostinger/hvps-openclaw:latest on Linux/Docker.
Summary
Setting
agents.defaults.heartbeat.everyto a duration greater than ~24.85 days (Node.js's signed-32-bitsetTimeoutcap of 2,147,483,647 ms) causes the heartbeat scheduler to fire in a tight loop and eventually crashes the gateway withOpenClaw exited with code 1. The container wrapper does not auto-respawn the gateway after exit. The CLI silently falls back to embedded mode on the next invocation.Reproduction
openclaw.json, set:{ "agents": { "defaults": { "heartbeat": { "every": "365d" } } } }Observed behaviour
Container logs flood with thousands of lines like:
Eventually the gateway exits:
After this, port 18789 is not listening; subsequent
openclaw agentinvocations silently fall back to embedded mode (see related issue on silent embedded fallback).docker exec ... ps -efshows no gateway process; only the proxynode server.mjsremains.Root cause
Per Node.js docs,
setTimeout(fn, delay)clampsdelay > 2147483647to1ms. The heartbeat scheduler appears to compute "next fire = now + every" and pass it directly tosetTimeout, so the very-large delay gets truncated to 1 ms. The function then runs immediately, recomputes, and re-arms — a tight loop that ultimately exhausts something (event loop / promise queue / heap) and the process dies.Expected behaviour
At least one of:
loadConfigif the resolved ms exceeds Node's setTimeout limit.2^31-1ms with a warning.Additionally, the wrapper / supervisor should auto-respawn the gateway after
exit code 1instead of leaving the proxy alive but the gateway dead.Workaround
Use a value safely under the cap:
"every": "24d"(≈ 2,073,600,000 ms) is safe and still effectively "never" for a "durably disabled" heartbeat.Environment
OpenClaw 2026.4.12 (
1c0672b), running in containerghcr.io/hostinger/hvps-openclaw:lateston Linux/Docker.