Long sessions leak one pty per command → pty pool exhausted, no terminal can launch

Resolved 💬 2 comments Opened Jun 13, 2026 by Zab1neh Closed Jun 17, 2026

Environment

  • Claude Code 2.1.167
  • macOS 26.4 (build 25E246), Apple Silicon

Summary

On a long session that ran many Bash / run_in_background commands, the Claude Code process leaked the entire pseudo-terminal pool (one pty per command, never released). Once the OS pty ceiling is hit, no terminal app on the machine can launch a shell.

Symptom

  • Terminal.app: [forkpty: Device not configured] / "Could not create a new process and open a pseudo-tty."
  • iTerm2 (freshly installed to test): window opens, then "Session Ended" immediately — same root cause, not app-specific.

Root cause (diagnosed live, while the leak was active)

$ sysctl kern.tty.ptmx_max
kern.tty.ptmx_max: 511                 # the ceiling

$ ls /dev/ttys* | wc -l
527                                    # allocated, over the ceiling

$ lsof /dev/ptmx | awk 'NR>1{print $1}' | sort | uniq -c | sort -rn
511 Claude                             # one app, holding the whole pool

# broken down by PID:
$ lsof /dev/ptmx | awk '$1=="Claude"{print $2}' | sort | uniq -c
511 75981                              # a SINGLE Claude PID holds all 511

So one Claude Code process held 511 of 511 ptys — one pseudo-terminal allocated per Bash/background command, accumulated for the process lifetime, never freed on command completion. When the pool is exhausted, forkpty() fails system-wide.

Reproduction

Run a long Claude Code session with many tool calls — especially repeated run_in_background commands (in my case: dozens of background panel/dogfood runs + ssh + foreground Bash) — until the pty count approaches kern.tty.ptmx_max. Then try to open any terminal: it fails to allocate a pty.

Workaround

  • Cmd+Q the Claude Code app → releases all leaked ptys (count dropped from 511 to 22). Or reboot.
  • The leak cannot be cleared from within the session, because the process holding the ptys is the session itself — killing it ends the session.

Expected behavior

The pty allocated for a command should be released when that command completes, not retained for the lifetime of the Claude Code process.

Impact

  • Blocks all terminal usage machine-wide once exhausted.
  • Specifically breaks the operator-terminal git commit flow that a pre-commit hook's interactive prompt depends on (the hook opens /dev/tty, which can't be allocated) — so even a documented terminal-approval path is blocked until the app is restarted.

View original on GitHub ↗

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