PTY master FDs leak on long sessions — exhausts kern.tty.ptmx_max on macOS
Summary
The Claude Code host process leaks /dev/ptmx master file descriptors across tool invocations. On a long-running session, accumulated FDs exhaust macOS's kern.tty.ptmx_max (default 511), after which forkpty() returns ENXIO ("Device not configured") system-wide — new Terminal windows, sudo, SSH sessions, and Claude's own Bash/Agent spawns all fail until the process is killed or the machine is rebooted.
Environment
- Claude Code: 2.1.136
- macOS: 26.4.1 (build 25E253), arm64 (Apple silicon)
- Kernel: Darwin 25.4.0
kern.tty.ptmx_max: 511 (macOS default)
Reproduction
- Start a Claude Code session.
- Use it heavily — many Bash tool calls, Agent spawns, MCP-backed tools.
- Periodically run
lsof /dev/ptmxfrom another terminal. - Observe Claude's open
/dev/ptmxFDs grow monotonically and never decrease, even after the corresponding child processes have exited.
Evidence
lsof /dev/ptmx snapshot from one such session (single Claude PID 54656):
Claude 54656 zacharypucci 1673u CHR 15,10 0t0 605 /dev/ptmx
Claude 54656 zacharypucci 1650u CHR 15,510 0t0 605 /dev/ptmx
Claude 54656 zacharypucci 1647u CHR 15,509 0t0 605 /dev/ptmx
Claude 54656 zacharypucci 1633u CHR 15,503 0t0 605 /dev/ptmx
Claude 54656 zacharypucci 1631u CHR 15,507 0t0 605 /dev/ptmx
Claude 54656 zacharypucci 1629u CHR 15,3 0t0 605 /dev/ptmx
Claude 54656 zacharypucci 1628u CHR 15,505 0t0 605 /dev/ptmx
Claude 54656 zacharypucci 1618u CHR 15,499 0t0 605 /dev/ptmx
Claude 54656 zacharypucci 1617u CHR 15,508 0t0 605 /dev/ptmx
Claude 54656 zacharypucci 1613u CHR 15,506 0t0 605 /dev/ptmx
... (continuing down through FDs 1588u, all to /dev/ptmx, each with a unique
device minor number — 15,490 / 15,493 / 15,495 / 15,496 / 15,497 / 15,498 /
15,499 / 15,501 / 15,503 / 15,504 / 15,505 / 15,506 / 15,507 / 15,508 / 15,509 /
15,510, ...)
- FD numbers in the 1500s+ range indicate sustained accumulation across the session.
- Each line has a unique PTY device minor — i.e. these are distinct PTY allocations, not dup'd handles to one PTY.
ps -axo pid,user,tty,command | awk '$3 ~ /^ttys/'at the same moment showed only 1 actually-attached tty session (the user's interactive zsh), confirming the children have exited and only Claude's parent-side master FDs remain.
Symptoms when limit is hit
$ sudo sysctl -w kern.tty.ptmx_max=4096
sudo: unable to allocate pty: Device not configured
# new Terminal.app window →
# forkpty: Device not configured
# Could not create a new process and open a pseudo-tty.
kern.tty.ptmx_max is read-only after boot on macOS, so the only userland remediation is killing the leaking Claude process or rebooting.
Likely cause
A node-pty / forkpty wrapper somewhere in the Bash / Agent / MCP-stdio spawn path is not closing the master FD in the child's exit handler. Each tool call leaks 1 FD.
Suggested fix
In whatever wraps PTY-backed subprocess spawning, ensure the master FD is explicitly close()d on child.on('exit') (and on error), not just when the wrapping object is garbage-collected — V8 GC is not timely enough to prevent accumulation at typical tool-call rates.
Workaround for users on macOS
Add to /etc/sysctl.conf (requires reboot to apply):
kern.tty.ptmx_max=4096
This raises the ceiling ~8× but does not fix the underlying leak.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗