> **App:** ChatGPT for Mac — **version `26.730.61639`** (build `6234`)
> **OS:** macOS 14.3.1 (build 23D60), Apple Silicon
## Summary
The ChatGPT macOS desktop app accumulates thousands of `` (zombie) child processes that are never reaped. After running for a while it exhausts the per-user process limit, after which **every `fork()` on the machine fails** with `resource temporarily unavailable` and the whole user session (Terminal, shells, editors) becomes unusable.
## Environment
- App: **ChatGPT for Mac, version 26.730.61639 (build 6234)**
- OS: **macOS 14.3.1 (build 23D60)**, Apple Silicon
- Account uses the **Codex CLI** subprocess feature
## Steps to reproduce
1. Launch ChatGPT for Mac and use it (including Codex CLI features) over an extended session (hours / days), keeping the app running.
2. At some point the system hits the per-user process limit and `fork` starts failing system-wide.
## Observed behavior
- The main ChatGPT process (`/Applications/ChatGPT.app/Contents/MacOS/ChatGPT`) spawns child processes (mostly Codex CLI workers under `/Applications/ChatGPT.app/Contents/Frameworks/Codex`, plus `bare-modifier-monitor`, `codex`, `ssh`) but **does not `wait()` on them**, so exited children become zombies.
- Zombies still occupy process-table slots and count toward `kern.maxprocperuid`.
### Evidence (snapshot taken while the failure was occurring)
```
$ ulimit -u # → 5333 (per-user hard limit)
$ sysctl kern.maxproc kern.maxprocperuid
kern.maxproc: 8000
kern.maxprocperuid: 5333
$ ps -axo user,pid,ppid,comm > /tmp/ps_dump.txt
$ awk ‘$1==“lijiapei”{c[$4]++; p[$3]++} END{ … }’ /tmp/ps_dump.txt
# children grouped by parent pid:
4913 ppid=84130 # ← the ChatGPT main process
339 ppid=1 # launchd (normal)
$ awk ‘$2==“84130”{print}’ /tmp/ps_dump.txt
lijiapei 84130 1 /Applications/ChatGPT.app/Contents/MacOS/ChatGPT
# children of 41import4130, grouped by command:
4901 # ← zombies
9 /Applications/ChatGPT.app/Contents/Frameworks/Codex
1 /Applications/ChatGPT.app/Contents/Resources/codex
1 /Applications/ChatGPT.app/Contents/Resources/native/bare-modifier-monitor
1 /usr/bin/ssh
```
- Total processes owned by the user: **5331 / 5333** (at the limit).
- The parent (ChatGPT main, PID 84130) holds **4913 children**, of which **4901 are defunct**.
## Impact
- Once the limit is reached, **no process can be spawned by any app in the user session**:
```
_omz_async_request:38: fork failed: resource temporarily unavailable
zsh: fork failed: resource temporarily unavailable
```
- Terminal, shells, Oh My Zsh async prompt, build tools (Gradle/Android Studio, etc.) all fail to launch subprocesses.
- The machine appears “broken” until the ChatGPT process is killed.
## Expected behavior
ChatGPT should `wait()` / reap its child processes (Codex workers, monitors, ssh) so they don’t accumulate as zombies. A long-running desktop app must keep a bounded, stable process count.
## Workaround
Kill the ChatGPT main process; its zombies are re-parented to launchd and reaped immediately, freeing the process table:
```
kill -9
```
(`kill` is a shell builtin, so it still works when fork is failing.) Then relaunch ChatGPT.
## Suggested fix
- Ensure all spawned subprocesses (Codex CLI workers, `bare-modifier-monitor`, `codex`, `ssh`) are tracked and reaped (`waitpid`) on exit, or spawned under a supervisor that reaps them.
- Add a bound / pool for Codex worker processes and clean them up on session end.
- Consider logging/telemetry when child reaping fails.
## Logs / diagnostics available on request
- Full `ps -axo user,pid,ppid,comm` snapshot
- Aggregation by command and by parent pid
- Sample of the failing shell output