Gateway RSS growth freezes the event loop: process accepts TCP but cannot complete a websocket handshake
Environment
- openclaw 2026.9.2 (3928bad)
- Node 22.23.2, Debian 12
- Long-running
openclaw gateway rununder a supervisor, Telegram + WhatsApp channels, several concurrent agent sessions
Summary
Gateway RSS grows under load until major GC pauses freeze the event loop for minutes at a time. While frozen the process still holds its listening socket, so it accepts TCP but cannot complete a websocket handshake. From outside it looks like the gateway is up; in practice nothing is dispatched.
A scheduled restart clears it and the cycle repeats.
Evidence
Peak RSS against event-loop stalls on one host, same day:
| Time | RSS | Longest stall in that window |
|---|---|---|
| 03:03 | 3,178 MB | 164,367 ms |
| 04:07 | 2,173 MB | 78,373 ms |
| 06:33 | 1,592 MB | 34,541 ms |
Stalls are reported by the gateway's own diagnostic:
liveness heartbeat delayed: overdue=134297ms elapsed=164367ms; deferring recovery decisions
and clients see:
[openclaw] Could not start the CLI.
[openclaw] Reason: Opening handshake has timed out
The openclaw --version path still answers instantly during these windows, so the process is alive; only work that needs the event loop is blocked.
Growth rate
Growth tracks failure volume rather than uptime. A quiet 90-minute window held flat at about 1.6 GB, while a window with a dispatch failure storm took one process from 619 MB to 4,670 MB in 54 minutes before it died.
What I ruled out
- Database contention. Hot queries on the state SQLite run in 2–4 ms in isolation, plans use their indexes, fragmentation is 0.5%, and the WAL is stable. The slow-transaction rate is flat across a whole day and does not correlate with the stalls.
- A specific hung tool call. The stalls occur with no child processes running.
- Disk pressure. Present on the host at one point, but the stalls continued unchanged after it was resolved with 200 GB free.
Why it is awkward operationally
The obvious mitigation is a memory-triggered restart, but the restart path itself needs the gateway to answer a health query in order to confirm it is idle. A gateway in this state cannot answer, so the safety interlock refuses and the only remedy is blocked exactly when it is needed. That is a separate design point, but it is why this failure tends to require manual intervention.
What would help
Some way to bound or observe the growth would make this manageable even without a root-cause fix: a heap breakdown exposed on the diagnostic endpoint, or an explicit cap on whatever accumulates per session, so operators can see what is retained rather than inferring it from RSS.
Happy to capture a heap snapshot from a live instance if that would be useful, or to run a build with extra instrumentation.
Source: openclaw/openclaw