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

    area:platform-linuxLinux-specific: Wayland, X11, Ubuntu, Fedora, Alpine, ARM, terminal emulatorsarea:toolsBuilt-in tools: file editing, shell, search, LSP, git, and tool call behavior

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions