Skip to content

copilot CLI 1.0.71 does not reap child processes — zombies accumulate under the copilot PID #4163

Description

@radtka2-mdt

Describe the bug

finished subprocesses accumulate as zombies ( state=Z ) parented to the copilot process
every session leaks independently (~2/min)
──────────────────────────
copilot PID │ age │ zombies it owns
──────────────────────────
3111579 │ 21 min │ 8
─────────────────────────
3017091 │ 1h13m │ 79
──────────────────────────
3025749 │ 1h09m │ 135
──────────────────────────

Impact:

  • Misleading "still running" status; every tool call needs a manual stop.
  • Slow, unreliable command completion.
  • Unbounded PID growth over long/multi-session use (pid_max headroom saves most hosts —
    here pid_max=4194304 with only ~2300 PIDs used — but a long day of many sessions could
    approach limits).

Affected version

copilot 1.0.71

Steps to reproduce the behavior

Steps to reproduce:

  1. Start copilot and run any prompt that triggers tool calls spawning subprocesses
    (git status, echo, python, etc. — even a trivial echo hi; true).
  2. Let the command run. Its stdout prints normally and the process exits (exit 0).
  3. Observe in another terminal:
    ps -u -o pid,ppid,stat,cmd | awk '$3 ~ /Z/'
    Finished children show as <defunct> (state Z), parented directly to the copilot PID.
  4. The CLI also reports each finished command as "still running after N seconds"
    indefinitely, requiring a manual stop.

Expected behavior

Observed behavior:

  • Subprocesses become zombies and are never reaped for the life of the session.
  • Each session leaks independently (~2 zombies/min under active use). Three concurrent
    sessions on one host held 8, 79, and 135 zombies (~222 total).
  • Verified via /proc: state=Z, wchan=0 — i.e. dead-and-unreaped, NOT blocked on I/O.
  • Only cleared by exiting the owning copilot process (zombies can't be killed).

Not NFS/fsync latency (objection preempted):

  • Affected children are in state Z (already returned from exit(), wchan=0), so all
    teardown incl. any fsync/stat is already complete — a process cannot be "mid-fsync"
    and be a zombie.
  • They persist for the entire session, not ~30s (oldest observed: 86 min, etime 01:26:38).
  • A child running only the shell builtin true (zero file I/O — nothing to fsync/stat)
    still zombies; meanwhile a properly-wait()ing parent reaps an identical bash -c true
    child in <1s on the same NFS filesystem.
  • The "~30s" some observers report is the tool's own initial_wait poll window, after
    which it prints "still running" — not the shell taking 30s to exit.

Expected behavior:

  • After a spawned subprocess exits, copilot should wait()/reap it so no zombie remains.
  • The corresponding tool call should be marked completed promptly once the child exits,
    rather than reported as "still running" indefinitely.

Additional context

Environment:

  • copilot 1.0.71 (latest on npm as of 2026-07-17)
  • Node v24.16.0 (nvm)
  • RHEL 8.10, kernel 4.18.0-553.141.1.el8_10.x86_64, x86_64
  • copilot binary: ~/apps/bin/copilot; launched from an interactive bash (no systemd scope)
  • Home dir on NFS (weka/home) — see "Not NFS" note below; ruled out as the cause

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions