app-server-broker.mjs processes leak when Claude Code session ends abnormally
Summary
app-server-broker.mjs processes (spawned by the Codex plugin) accumulate indefinitely when a Claude Code session terminates abnormally. They are never cleaned up, consuming significant RAM and swap.
Root cause
The broker is intentionally daemonized (PPID=1) so it survives across reconnects. Its only exit paths are:
- A
broker/shutdownRPC message sent by Claude Code - SIGTERM or SIGINT
- Fatal startup error
There is no idle timeout and no parent-death detection. If Claude Code exits via crash, OOM kill, SIGKILL, or terminal close before sending broker/shutdown, the broker runs forever — listening on a stale Unix socket that no client will ever reconnect to.
Evidence
On a single developer machine running CoachClaw (a Claude Code-heavy project), 38 orphaned brokers accumulated over ~2.5 days:
$ ps -eo pid,ppid,etime,cmd | grep app-server-broker | grep -v grep | wc -l
38
# All had PPID=1 (orphaned), oldest was 2d 15h
Combined resource usage of the 99 broker+child processes:
- ~1.35 GB RSS
- ~3.8 GB swap consumed (on a 4 GB swap device — completely exhausted)
After pkill -f app-server-broker.mjs:
- Free RAM: 281 MiB → 4.0 GiB
- Swap used: 4.0 GiB → 207 MiB
Repro
- Start any Claude Code session that triggers a Codex tool call (spawns a broker)
- Kill the terminal, trigger an OOM, or otherwise hard-exit Claude Code without graceful shutdown
- Observe that the
app-server-broker.mjsprocess remains running indefinitely with PPID=1
Suggested fix
Add an idle-timeout to the broker: if zero clients have been connected for N minutes (e.g. 15–30), call shutdown() and exit. This is a safe safety net — a live Claude Code session will reconnect well within that window, so legitimate brokers are unaffected.
// Example sketch — after server.listen():
let lastActivityAt = Date.now();
// reset on every socket connect/disconnect
const idleCheckInterval = setInterval(() => {
if (sockets.size === 0 && Date.now() - lastActivityAt > IDLE_TIMEOUT_MS) {
clearInterval(idleCheckInterval);
shutdown(server).then(() => process.exit(0));
}
}, 60_000);
Workaround
A cron job running every 15 minutes that kills brokers older than 30 minutes:
ps -eo pid,etimes,cmd --no-headers \
| awk '/app-server-broker\.mjs/ && $2 > 1800 {print $1}' \
| xargs -r kill
Environment
- Claude Code CLI (latest)
- Linux 6.8.0-110-generic
- Codex plugin v1.0.2
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗