New request
#2577: [bug] api/terminal.py: Node child processes become zombies when Popen handle is not reaped (same class as #1912)
We'll provision a sandbox, run an agent against the issue, and open a draft PR. You can pull the branch and iterate from there.
Sonnet analysis.
Description
Observed two [node] <defunct> zombie processes (PIDs 2860182, 2909786) as children of the hermes-webui server process (PID 2872805), both dating from May 13. The Python parent is still alive but has never reaped these children.
This is the same class of bug identified in #1912 for api/providers.py, but in api/terminal.py, which uses a persistent subprocess.Popen handle (line 42) to manage long-lived processes.
Root cause
api/terminal.py spawns Node child processes via subprocess.Popen but does not guarantee .wait() is called when the child exits — particularly when the terminal session is closed or an error path is taken. The Python parent remains alive, so the kernel cannot auto-reap the children, leaving them as zombies.
Missing mitigations (per #1912)
- No
preexec_fn=_set_pdeathsig— Node children survive parent SIGKILL - No
.wait()/communicate()call on the cleanup path - No
stdin=DEVNULL
Suggested fix
Apply the same prctl(PR_SET_PDEATHSIG) pattern already recommended in #1912, plus ensure the cleanup path always calls .wait():
import ctypes, signal
def _set_pdeathsig():
PR_SET_PDEATHSIG = 1
ctypes.CDLL("libc.so.6").prctl(PR_SET_PDEATHSIG, signal.SIGTERM)
proc = subprocess.Popen(
[...],
preexec_fn=_set_pdeathsig,
stdin=subprocess.DEVNULL,
)
References
- #1912 — same pattern in
api/providers.py - #1873 — original subprocess isolation PR
This is a localized process-reaping bug in api/terminal.py that can be fixed by applying the established subprocess lifecycle pattern.
- api/terminal.py
- tests/test_terminal.py