PTY master FDs leak on long sessions — exhausts kern.tty.ptmx_max on macOS

Resolved 💬 3 comments Opened May 15, 2026 by zakpucci Closed May 19, 2026

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

  1. Start a Claude Code session.
  2. Use it heavily — many Bash tool calls, Agent spawns, MCP-backed tools.
  3. Periodically run lsof /dev/ptmx from another terminal.
  4. Observe Claude's open /dev/ptmx FDs 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗