| name | zombie-process-cleanup |
| description | Detecting and reaping zombie/orphan processes in agent sandboxes. SIGCHLD handling, waitpid reaping, subreaper patterns, and Node.js child_process cleanup. Sources: node-modules/zombie, Linux waitpid(2) man page. |
/zombie-process-cleanup
When to Use
- Agent spawns many short-lived subprocesses that become zombies
- Sandbox PID namespace filling up with defunct processes
- Long-running agent sessions accumulating dead children
- Implementing a sub-reaper for an isolated PID namespace
Do NOT use for
- Processes still running (zombies are already dead, just not reaped)
- Kernel threads (not reapable from userspace)
Node.js child_process cleanup
import { spawn } from 'child_process'
function spawnSafe(cmd: string, args: string[]): Promise<number> {
return new Promise((resolve, reject) => {
const child = spawn(cmd, args, {
detached: false,
stdio: 'pipe',
})
child.on('error', reject)
child.on('close', (code, signal) => {
resolve(code ?? (signal ? 128 : 0))
})
process.on('exit', () => { try { child.kill('SIGTERM') } catch {} })
})
}
Bash sub-reaper (PID namespace)
#!/usr/bin/env bash
reap_children() {
while true; do
wait -n 2>/dev/null || break
done
}
trap reap_children SIGCHLD
"$@" &
MAIN_PID=$!
wait "$MAIN_PID"
MAIN_EXIT=$?
reap_children
exit "$MAIN_EXIT"
Detect zombies
detect_zombies() {
local zombies
zombies=$(ps -eo pid,ppid,stat,comm | awk '$3~/^Z/{print}')
if [[ -n "$zombies" ]]; then
echo "[zombie] Detected defunct processes:" >&2
echo "$zombies" >&2
return 1
fi
return 0
}
reap_my_children() {
while IFS= read -r pid; do
wait "$pid" 2>/dev/null && echo "[reap] reaped PID $pid"
done < <(ps -eo pid,ppid,stat | awk -v me="$$" '$2==me && $3~/^Z/{print $1}')
}
Anti-Fake-Pass Checklist
❌ spawn() with detached:true but no unref() → child still prevents parent exit
❌ No close handler → process reaped by OS eventually but leak accumulates
❌ exec() without fork → replaces parent, no child to reap
❌ Zombie reaping requires the parent (not grandparent) to call wait()
❌ PID namespace without subreaper → orphans reparent to PID 1 (container init)
❌ SIGCHLD ignored (SIG_IGN) on Linux auto-reaps but breaks wait() return codes