Repository navigation
server: PID 1 does not reap orphaned grandchildren, leaving zombies and shells stuck "running" #53564
Description
Summary
When opencode serve runs as container PID 1 (no init / tini), orphaned shell descendants that get reparented to PID 1 are never reaped. They stay as zombies (<defunct>, PPid=1) and the corresponding shell/tool call stays status=running in the UI forever. Only restarting the container clears it.
Environment
- opencode version: 2.0.24
- OS: Linux 6.18.18.c1107-trim, x86_64 (Debian 12 container, Docker)
- Terminal: Unavailable: server mode, no terminal env vars set
- Shell: Unavailable:
$SHELLunset in container - Install/channel: release image
ghcr.io/anomalyco/opencode:2.0.24, run via Docker Compose; containerEntrypoint=[opencode],Cmd=[serve --hostname 0.0.0.0 --port 4096], noinit: true - Active plugins: none found (
"plugins": [])
Reproduction
- Run
opencode serveas PID 1 in a container without an init process (noinit: true). Confirmcat /proc/1/commshowsopencode. - In a session, start a command whose process tree has an intermediate shell, e.g.
sh -c 'sleep 300; echo done'(this is how a background shell with a wrapper looks). - Kill the intermediate shell only:
kill -9 <pid-of-sh>. The innersleepis orphaned and reparented to PID 1. - Let the orphaned
sleepexit (or kill it as well). It becomes a zombie:ps -o pid,ppid,statshowsZwithPPID 1. - Observe the shell/tool call in the UI stay
status=runningindefinitely.
Expected Behavior
PID 1 (the opencode process) should reap orphaned children, either by installing a SIGCHLD handler / calling wait(), or by shipping with an init. Exited shells should be marked completed in the UI; no zombies should accumulate.
Actual Behavior
Zombies accumulate under PID 1 and the shell list keeps showing them as running.
Observed before restart (4 zombies, all PPid=1):
PID PPID STAT COMMAND
815 1 Z [ssl_client]
2123 1 Z [sleep]
4712 1 Z [sleep]
7804 1 Z [sleep]
After docker restart opencode-v2: zero zombies, and the stale "running" entries in the UI disappear. Reproducible every time the intermediate shell is killed this way; it does not happen for commands that exit normally while their parent is still alive (the parent reaps them).
Additional Context
- This is distinct from fix(opencode): zombie processes from detached shell children on Linux #29506 (closed:
detached: truechildren not reaped) and bash.ts: detached=true leaves zombie processes when command times out #30868 (closed: detached children on timeout). Those describe detached children where the parent is still alive; here the child is orphaned to PID 1, so reaping is PID 1's responsibility and there is no init to do it. - Possibly related: Shell tool stays status=running after a fast-exiting command with no surviving descendant (1.18.30, server mode) #50424 (open: shell tool stuck
status=running) — but that report describes a descendant holding stdio pipes, not zombie reparenting to PID 1. - Workaround: set
init: trueon the container so Docker inserts tini as PID 1 and reaps orphans. Confirmed to prevent the accumulation. - Suggest documenting the
init: truerequirement whenopencode serveis used as PID 1, or adding a SIGCHLD reaper in the server.
Captured with the opencode issue-report skill. Diagnostics collected on 2.0.24 in the container described above.
Activity
wthsama commentedon Oct 12, 2026
Re-checked on 2.0.27 (latest V2 tag, 2026-10-12): still not addressed.
packages/cli/Dockerfile@v2.0.27is unchanged from 2.0.24 —apk add --no-cache libgcc libstdc++ ripgrep, thenENTRYPOINT ["opencode"]. Notini, no--init, so the official image still runsopencodeas PID 1.SIGCHLDhas zero matches in the repo, and neitherpackages/util/src/process.tsnorpackages/server/src/process.tsinstalls aSIGCHLD/wait()reaper or ships an init.reap/zombie/SIGCHLD/orphan/init→ 0 matches).Workaround is unchanged: run with an init (
init: truein Compose, ortinias the entrypoint). A two-line image fix would beapk add tini+ENTRYPOINT ["tini", "--", "opencode"]inpackages/cli/Dockerfile.opencodejr commentedon Oct 27, 2026
Thanks for the detailed report. I can confirm the zombie part on 2.0.24, and it still happens on the latest
dev(f7d0ce1).What I did:
opencode serveas PID 1 of a fresh PID namespace, which matches a container withoutinit: true.sh -c 'sleep 47; echo done'through the server's shell API.kill -9on the intermediatesh, so thesleepwas reparented to PID 1, then killed thesleep.The
sleepstays<defunct>with PPID 1 and is never reaped. The release image's Dockerfile still hasENTRYPOINT ["opencode"]with no init in front of it. The script below reproduces this.I could not reproduce the shell staying
running. In every case the shell was reported asexited(SIGKILL) within about two seconds of killing the intermediatesh. That held when opencode was PID 1, when a bash parent stayed alive, and through the TUI shell tool. Since a zombie has already closed its file descriptors, the stuck status may come from something else, for example a descendant still holding the output open, as discussed around #50424. If you can share the exact command (and whether it ran as a background shell) that left an entry stuck asrunning, that would help.Until this is addressed,
init: true/docker run --initremains a working workaround.Reproduction script (pid1-zombies.sh)
Run it:
bash pid1-zombies.sh