claude remote-control: session-host RSS leaks ~1GB/day while idle, eventually wedging the relay
What happens
claude remote-control (the always-on session host, run as a systemd service) leaks resident memory steadily while idle, then wedges: the process stays alive and systemd reports active (running), but the relay connection is dead and the operator is locked out until a manual systemctl restart.
Environment
- claude 2.1.212 (Claude Code)
- Ubuntu 26.04 LTS, kernel 7.0.0
- systemd service running
claude remote-control --spawn worktree --capacity 8 … - always-on host, uptime measured in days/weeks
The leak (quantified by systemd's own accounting)
The host process grows steadily while hosting zero sessions (capacity 0/8, exactly one process in the service cgroup). systemd's per-lifetime Consumed … memory peak accounting across three consecutive runs of the same service:
- 1w 23h run → 1.8 GB peak
- 1w 2d run → 4 GB peak, 2 GB swap peak (the run that wedged)
- 8h run → 462 MB peak
Monotonic, roughly ~1 GB/day, released only by a restart. Because it grows with no sessions attached, it looks like host-process heap growth (buffers / websocket churn / retained objects), not accumulated session or conversation state.
The wedge (two modes observed)
- Memory-ceiling: approaching a cgroup memory limit it thrashed swap instead of OOMing; the relay websocket dropped and it spun
Reconnecting … disconnected 0sindefinitely. systemd never restarts it —Restart=on-failurenever fires, because the process never actually exits. - Silent: separately, at low memory (~130 MB RSS) the relay connection died with the process otherwise healthy and zero journal output — it simply stopped serving.
In both cases systemd shows active (running) the entire time, and the only recovery is a human noticing they are locked out and running sudo systemctl restart claude-remote-control.
Ask
- Investigate the steady RSS growth in the long-lived remote-control host (leak while idle / zero-session).
- Consider a built-in liveness/keepalive so a dead relay causes a clean exit (letting
Restart=recover it) instead of an indefinite alive-but-frozen reconnect loop.
Workarounds (in case useful to others)
MemorySwapMax=0+ aMemoryMaxset below the observed dysfunction point +OOMPolicy=kill+Restart=always, so the memory wedge self-heals.- An external liveness watchdog (checks the daemon holds an ESTABLISHED :443 connection to the relay; bounces it after N consecutive dead checks) to catch the silent mode.
- A nightly preventive restart to reset the leak clock.