Long sessions leak one pty per command → pty pool exhausted, no terminal can launch
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+Qthe 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 commitflow 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.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗