Pty (pseudo-terminal) fd leak exhausts macOS pool until forkpty fails system-wide
Summary
The Claude app's main process leaks pseudo-terminal (pty) master file descriptors without releasing them. Over ~26 hours of heavy agent/Bash-tool usage, a single long-lived process accumulated 508 open ptys, against macOS's system-wide cap of 511 (kern.tty.ptmx_max).
Once the pool is exhausted, forkpty() fails system-wide with ENXIO ("Device not configured") — so no application can open a new terminal. A fresh Terminal.app window failed with:
[forkpty: Device not configured]
[Could not create a new process and open a pseudo-tty.]
Environment
- macOS (Darwin 25.5.0), Apple Silicon
- Claude app, single main process running ~26h
- Heavy agent / Bash-tool usage during that period
Evidence
$ sysctl kern.tty.ptmx_max
kern.tty.ptmx_max: 511
$ lsof -n | grep /dev/ttys | awk '{print $1}' | sort | uniq -c | sort -rn | head
508 Claude
12 zsh
3 Devin
$ python3 -c "import os; os.openpty()"
OSError: [Errno 6] Device not configured # reproduces the same ENXIO failure
- The leaking process held 508 pty master fds while having 0 live child processes — the shells had all exited and been reaped, but their pty master fds were never
close()d. This is the signature of a per-invocation fd leak.
Likely cause
A pty appears to be allocated per Bash/terminal tool call, and the master fd is not closed after the child process exits — so ptys accumulate one-per-call until the cap is reached.
Workaround
- Quitting and relaunching the app releases all leaked ptys instantly (count dropped from 508 to 1, allocation restored). No reboot needed.
sudo sysctl -w kern.tty.ptmx_max=2048raises the cap temporarily as mitigation between restarts.
Expected behavior
pty master fds should be closed when their child process exits. Steady-state pty count should stay low regardless of session length.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗