PTY leak: Bash tool leaks master PTY file descriptors, exhausting kern.tty.ptmx_max

Open 💬 0 comments Opened Jun 23, 2026 by fdebane

Bug

Each Bash tool invocation opens a PTY master (/dev/ptmx) but never closes the file descriptor after the subprocess exits. Over a long session, leaked FDs accumulate until the kernel's kern.tty.ptmx_max pool is exhausted. At that point, Terminal.app (and any other app calling forkpty) fails with:

[forkpty: Device not configured]
[Could not create a new process and open a pseudo-tty.]

Reproduction

Run a Claude Code desktop session with many Bash tool calls, then:

lsof /dev/ptmx | grep Claude | wc -l

Evidence (this session)

  • Claude PID 87601 held 518 open /dev/ptmx file descriptors
  • kern.tty.ptmx_max = 511 → pool fully exhausted
  • 527 /dev/ttys* devices present vs 511 max
  • sudo sysctl -w kern.tty.ptmx_max=... itself failed (also needs PTY)
  • Workaround: osascript -e 'do shell script "sysctl -w kern.tty.ptmx_max=600" with administrator privileges'
lsof /dev/ptmx output (truncated):
COMMAND   PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
Claude    87601  franck  42u  CHR  15,7        0t0  605 /dev/ptmx
Claude    87601  franck  46u  CHR  15,4        0t0  605 /dev/ptmx
Claude    87601  franck  73u  CHR  15,9        0t0  605 /dev/ptmx
... (518 total entries for PID 87601)

Environment

  • Claude Code: 2.1.119
  • macOS: 26.5.1 (Darwin 25.5.0, Build 25F80)
  • Hardware: MacBook M2

Fix

Close the PTY master FD in the Bash tool after the child process exits. The master should be closed once the subprocess's stdout/stderr are fully drained and the process has been wait()ed.

Impact

Terminal.app becomes unusable mid-session. Workaround requires admin privileges or a full restart of Claude Code.

View original on GitHub ↗