[BUG] Claude desktop app leaks pseudo-terminal (pty) master fds, exhausting macOS `kern.tty.ptmx_max` and locking out all terminal apps
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Summary
The Claude desktop app opens a pseudo-terminal master (/dev/ptmx) for shell/agent
operations and never closes it. The master fds accumulate for the lifetime of the app
process. After a few days of normal use they exhaust macOS's system-wide pty limit
(kern.tty.ptmx_max, default 511), at which point no application on the machine can open
a new terminal — Terminal.app and iTerm2 windows open and immediately die because the
kernel can't allocate a tty.
Impact / severity — High
This is not contained to Claude. Once the cap is hit, any app needing a pty fails
system-wide (Terminal.app, iTerm2, VS Code/Cursor integrated terminals, ssh sessions, etc.).
The failure is silent and confusing — the apps look "broken" with no error and no crash log.
Recovery requires quitting the Claude app or rebooting. Users are unlikely to connect their
inability to open a terminal to the Claude desktop app.
Likely root cause
A pty master (probably via node-pty / Electron) is created per shell/agent/Bash/MCP
operation, but the master file descriptor is not closed when the corresponding process ends.
Over a long-lived app session these leak until kern.tty.ptmx_max is exhausted.
Suggested fix
Ensure the pty master fd is closed when its child process exits (close-on-exit for every
spawned shell/agent/MCP pty), and consider an idle-session reaper as a safety net.
What Should Happen?
- Expected: the pty master is closed when its child shell / agent command / MCP stdio
process exits, returning the slot to the kernel.
- Actual: the master fd stays open for the lifetime of the app process and accumulates
without bound.
Error Messages/Logs
#### Evidence (from an affected machine)
App process, ~3 days uptime, holding 519 pty masters:
$ ps -p <pid> -o pid=,etime=,comm=
<pid> 03-03:23:59 /Applications/Claude.app/Contents/MacOS/Claude
$ lsof -nP -p <pid> | grep -c /dev/ptmx
519
Attribution across all processes — the app holds ~all allocated ptys, while only ~6 are
backed by a live tty session:
$ lsof -nP | grep /dev/ptmx | awk '{print $1}' | sort | uniq -c | sort -rn | head
519 Claude
6 Cursor
4 zsh (x several)
1 Terminal
open /dev/ptmx masters total: 527 distinct slave ttys actually open: 6
Kernel refusing new ptys when the cap is reached:
$ python3 -c 'import os; os.openpty()'
OSError: [Errno 6] Device not configured # ENXIO — no free pty slots
The count was observed growing live (516 → 519 masters over a handful of tool calls in a
single session), confirming masters are opened per-operation and not released.
Steps to Reproduce
- Use the Claude desktop app over several days without quitting it (a long-lived app process).
- Run agent/Bash/terminal operations during that time (each appears to open one pty master).
- Observe the count of open
/dev/ptmxmasters held by the app climb by roughly one per
shell operation, ~150–170/day, and never decrease.
- Once total allocated ptys reaches
kern.tty.ptmx_max(511), Terminal.app and iTerm2 stop
opening — window appears then instantly closes; no crash report is generated.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.170
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Environment
- Claude desktop app: 1.11847.5
- Claude Code (running inside it): 2.1.170
- macOS: 26.1 (build 25B78)
kern.tty.ptmx_max: 511 (macOS default; hard max is 999)
Workarounds (for affected users)
- Immediate, no reboot: fully quit and reopen the Claude desktop app (Cmd-Q) — releases
all leaked masters at once.
- Temporary headroom:
sudo sysctl -w kern.tty.ptmx_max=999(999 is the kernel max;
values above it return EINVAL). Resets on reboot.
- A reboot also clears all leaked ptys.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗