Skip to content

opencode-cli serve accumulates thousands of <defunct> direct children under heavy file watcher activity #25883

Closed as not planned

Description

@ChrisJamesHassell

Description

opencode-cli serve (the desktop backend) keeps thousands of <defunct> direct children alive on macOS during heavy file activity. They don't get reaped until activity dies down for several minutes.

I hit this while running make proto (a code generator that touches a lot of files) inside an OpenCode session on a large monorepo. Watching the process tree:

$ ps -eo pid,ppid,state | awk '$2==56151 && $3=="Z"' | wc -l
4978

All 4,978 zombies were direct children of opencode-cli (PID 56151):

49049 56151 <defunct>
49050 56151 <defunct>
49051 56151 <defunct>
49052 56151 <defunct>
... (4,974 more)

The state breakdown of children of 56151 at one snapshot:

   2 ?Es   (errored)
  53 Rs    (running)
   1 Ss
   1 Ss+
  78 Us    (uninterruptible disk wait)
 482 Z     (zombies)

Parent process at the same time:

PID    %CPU  %MEM  ELAPSED   COMMAND
56151  102.1 20.7  23h 31m   /Applications/OpenCode.app/Contents/MacOS/opencode-cli ... serve --hostname 127.0.0.1 --port 55161

The CPU stays elevated even after make proto finishes. I assume because the parent is now spending cycles managing the zombie child table. After about 5 minutes of idle the count drops back to zero, but the same pattern reproduces immediately on the next file-write burst.

The git fan-out side of this (the reason so many children are exiting concurrently) is already covered by #21699 and #24739, so I'm filing this separately for the reaping behavior specifically. Even if the fan-out is reduced via debouncing, a parent process that lets thousands of zombies pile up under any meaningful concurrency is a separate bug.

I poked at packages/core/src/cross-spawn-spawner.ts. Children are spawned with detached: true on Mac/Linux and the lifecycle relies on libuv's SIGCHLD reaping via the 'close' event. The release path in acquireRelease doesn't actively waitpid if the deferred is already done, it just trusts libuv. With thousands of children exiting in rapid succession, libuv seems to fall behind. I didn't dig deep enough to confirm whether it's a libuv backpressure issue or the spawner holding refs that prevent reaping.

Plugins

@opencode-ai/plugin@1.4.8 only.

OpenCode version

1.14.33

Steps to reproduce

  1. Open a large repo in OpenCode Desktop.
  2. From a separate terminal in any worktree, run something that writes many files quickly (make, cargo build, prettier --write ., codegen, etc.).
  3. Watch direct children of opencode-cli:
PID=$(pgrep -f opencode-cli | head -1)
while true; do
  ps -p $PID -o pid,pcpu,pmem,etime,comm | tail -n 1
  ps -eo ppid,state | awk -v p=$PID '$1==p && $2=="Z"' | wc -l
  sleep 1
done

Zombie count climbs into the thousands during the burst and stays there for a few minutes after.

Screenshot and/or share link

Activity Monitor: 100% CPU, all cores, 89% system / 11% user time, load avg 172.63

The 89% system time vs 11% user split is the relevant bit. Most of that CPU is in fork/exec/wait syscalls, not in the JS-side patch builder. Load average hit 172.63.

Operating System

macOS 26.3.1 (Apple Silicon)

Terminal

N/A. Bug is in the desktop backend.

Activity

  1. github-actions commented on Jul 5, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions