Pty (pseudo-terminal) fd leak exhausts macOS pool until forkpty fails system-wide

Resolved 💬 2 comments Opened Jun 20, 2026 by tomchambers2 Closed Jun 20, 2026

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=2048 raises 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.

View original on GitHub ↗

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