Claude Desktop PTY Leak on macOS: 490 /dev/ptmx FDs After 4 Days of Use
This note documents a system-wide PTY file descriptor leak that was triggered after a long run on Claude desktop. The superficial symptom is that all terminal apps on macOS suddenly cannot open new windows. If you dig deeper, you will find that the Claude process has accumulated nearly 490 /dev/ptmx file descriptors in the past few days, occupying almost all the PTY available in the entire system. This article is expanded in the order of "Discovery → Diagnosis → Root Cause → Recurrence → Temporary Bypass → Suggestions for Repairers", which is convenient for other developers who encounter similar situations to quickly locate, and it is also convenient for the upstream team to review and repair.
1 phenomenon: all terminal apps suddenly cannot be opened

This happened on a macOS laptop that I use daily. The system has been running stably for a few days, with Claude desktop running all the time for daily work. During this period, I also used Claude Code to run many sessions in the terminal.
One afternoon, I wanted to open a new iTerm window and run git status. After iTerm started, only one line of error message flashed in the window and then it exited. The message was forkpty: Device not configured. Try changing Terminal.app and get the same error. VS Code's built-in terminal, tmux, and screen cannot open new sessions, and they all show the same Device not configured.
Commands in the terminal window that are already running can still be run, but as long as you try to fork a new pseudo terminal, it will fail immediately. This symptom of "the existing process is fine, but the new PTY is all hung" is very typical and points to the exhaustion of PTY resources.
2 Diagnosis, first look at the sysctl upper limit and then the occupancy

There is a kernel upper limit on the total number of PTYs on macOS, and sysctl can be read directly.
sysctl kern.tty.ptmx_max
kern.tty.ptmx_max: 511The system only allows a total of 511 PTYs to exist simultaneously. This value is a hard-coded small number on macOS, which is completely different from the tens of thousands of /proc/sys/kernel/pty/max on Linux. After realizing this, the next step is to see who is full.
/dev/ptmx is the entrance to apply for a new PTY. Each time it is opened, a new master/slave pair is allocated. Therefore, counting which processes hold the file descriptor of /dev/ptmx can locate the source of occupation.
lsof /dev/ptmx | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rnAfter running the results, the Claude process ranks first, with the number fluctuating around 490. The other processes add up to less than 20. In other words, Claude's desktop side has exhausted all PTY resources.
490 Claude
6iTerm2
3 tmux
1 sshd
...At this point, it is basically certain: Claude desktop opened the PTY in a certain usage scenario but did not close it, and the PTY handle was not released after the session ended. Over time, it has increased to close to the upper limit.
3 Root cause: PTY descriptor is not recycled

Continue to expand lsof to see the details of PTY held by Claude.
lsof -p <claude_pid> | grep ptmx | headEach line in the output is an independent descriptor of /dev/ptmx. The FD number increases from tens to hundreds, and these PTYs appear to be in an "unowned" state. They are no longer associated with any active Claude Code session or terminal window, but the handles are still hung on the Claude main process or its helper sub-process, and have not yet been closed.
The most likely code-level cause is that when Claude Desktop started Claude Code internally or other subtasks that required pseudo-terminals, it applied for PTY through forkpty or posix_openpt, but did not close the main-side descriptor correctly after the task ended. Every time you run a related task, one leak occurs. The more you run, the more serious the accumulation becomes.
From the perspective of the application layer, Claude users have no idea that a PTY is opened at the system layer every time they run the Claude Code command, let alone that these PTYs will become long-term leaked resources.
4 Recurrence conditions, long running time + frequent Claude Code sessions
This bug won't reappear in five minutes, it takes time to accumulate. According to the observation of this machine, the typical trigger path is as follows.
First, Claude desktop continues to run for many days without exiting. This is the norm for moderate-intensity users. Many people’s Mac restart interval is more than a week.
Second, Claude Code was used frequently during this period, including opening sessions within the Claude application, starting Claude Code commands in external terminals, running batch tasks in headless mode, etc. For each additional trigger scenario, the frequency of PTY applications increases.
Third, other applications in the system occasionally use PTY, such as iTerm, Cursor's built-in terminal, and remote ssh. These will overlap with Claude's leakage and accelerate the bottoming out.
After satisfying the first two items, it will take about 3-5 days to reach the upper limit of 511. The lsof result of this machine's current report is that Claude holds 490 PTYs, and other processes occupy about 15, for a total of 505. There are only 6 PTYs left from the upper limit of 511, so opening a new terminal will basically fail.
5 Environment information, precise version number
In order to facilitate the upstream team's comparison and troubleshooting, the key versions of this machine are listed here.
The operating system is macOS 15.7.3. Apple Silicon platform. The desktop version of Claude is subject to the "About" page of the application menu. At the time of this report, it is the latest version of the current stable channel. Claude Code CLI also uses the latest version built into the application or installed separately.
The reproduced sysctl value is the default configuration of macOS 15. There is no manual adjustment of kern.tty.ptmx_max. This value has remained 511 in recent macOS versions, and there is no launchctl to set a custom upper limit on this machine.
6 Temporary bypass, restart Claude and release immediately
After locating the root cause, the bypass solution is very straightforward. Completely exit the Claude desktop (including Quit Claude in the menu bar, rather than closing the main window) and let all Claude processes exit. The system will automatically recycle all PTY descriptors it holds.
Verify immediately after the operation and run lsof /dev/ptmx again. Claude's line has disappeared and the total number has dropped to about 20. Open iTerm and everything returns to normal, and the new terminal can fork normally.
But this bypass has obvious costs. Exiting Claude is equivalent to losing all ongoing conversation context, and the previous working session of Claude Code also needs to be re-established after reopening. For users who use Claude as their main work environment, it is almost equivalent to a workflow interruption.
The more interesting phenomenon is that as long as you do not exit Claude, even if you have used Activity Monitor to kill all terminal apps such as iTerm and Terminal, the new terminal still cannot be opened. Because the PTY handle is in the hands of Claude, not the terminal App.
7 Suggested Directions for Restorers
From the application code level, the possible repair directions include these.
The first is to put all forkpty or posix_openpt calls into a RAII wrapper with an explicit close. When the child process ends, the stream receives EOF, or the user interrupts the session, these exits must ensure that the master-side fd goes close. If node-pty is used on the Node.js side, make sure there is really close fd after .destroy() or .kill(), instead of just terminating the child process.
The second is to add watchdog. Claude's main process can periodically self-check the number of /dev/ptmx fds it holds. Once it exceeds the threshold (such as 50 or 100), it will alert in the log, which facilitates telemetry to collect the hit rate of real users. If it exceeds a higher threshold (such as 200), it will directly and actively close the old idle PTY.
The third is to give users a manual cleanup entry. Add a "Release Unused PTY" button in the settings or debugging panel. When users notice there is a problem with the system terminal, they can click this first without having to exit Claude entirely.
The fourth is to check the life cycle management of the helper sub-process. On macOS, Claude desktop uses multiple Helper processes. If the leak occurs in the Helper and the Helper is retained by the main process, the fd will be locked along with the Helper. Helper Recycling correctly when it's time to recycle can solve more than half of the problem.
8 Notes to developers at the system level
kern.tty.ptmx_max=511 on macOS is a very tight upper limit, not at the same order of magnitude as Linux. Any application that needs to launch pseudo-terminals in batches on macOS should design this number as a hard upper limit.
In theory, macOS allows root users to temporarily increase this value through sysctl, such as sudo sysctl -w kern.tty.ptmx_max=2048, but this is not a recommendation for end users. There is no need for ordinary users to modify system kernel parameters for a third-party application, and it is not recommended to do so.
Claude desktop is positioned as a product for all macOS users and should run safely within the 511 limit by default. A long-running app holding hundreds of PTYs is itself a design flaw worth fixing.
9 Impact assessment, not just the terminal cannot be opened
PTY exhaustion not only affects the scenario of "newly opening a terminal app", it also affects many workflows that rely on pseudo-terminals in a covert way.
Some npm scripts in the build tool will use PTY to run sub-commands. When the PTY is used up, they will fail with an error that is difficult to diagnose. Docker Desktop may also have problems if PTY is used when starting a new container. SSH remote login to the local machine or ssh from the local machine to the remote computer will fail because ssh will allocate a pseudo terminal by default.
Worse, these failures don't point to Claude. Users will only see "My npm install suddenly failed to install" and "My Docker started with an error". They will never think that the root cause is Claude occupying 490 PTYs in the background. This "indirect victimization" can make attribution of bug reports very difficult.
10 For those who also encounter this problem, a self-check list
If you read this article because you encountered the same "terminal suddenly cannot be opened" problem, the following self-check list can quickly determine whether it is the same bug.
The first step is to run sysctl kern.tty.ptmx_max to confirm that the macOS upper limit is about 511 and has not been increased by yourself.
The second step is to run lsof /dev/ptmx | wc -l. If the number is close to or exceeds 500, it means that the PTY is basically exhausted.
The third step is to run lsof /dev/ptmx | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn to see who is occupying the first place.
Step 4: If the first place is Claude and the number exceeds 100, it is basically the bug in this article. Completely exit the Claude desktop, and then immediately open a new terminal to test whether it is restored.
Step 5: If you are willing to provide the data to the upstream for repair, use lsof -p <claude_pid> | grep ptmx > /tmp/claude_pty_leak.txt before exiting to grab an archive of the current fd list and attach it when submitting the issue.
FAQ
Does this bug only appear on macOS?
The observed recurrences so far are all on macOS, because the upper limit of PTY in macOS is only 511, which is very easy to reach the top. On Linux, /proc/sys/kernel/pty/max usually starts at 4096. Even with the same leak rate, the triggering time will be delayed until it is difficult to detect. But it's essentially the same leak problem, but Linux users can't perceive it. There is no POSIX PTY on Windows. Claude desktop uses ConPTY on Windows. The leak path is different and needs to be investigated separately.
Can ordinary users prevent it in advance?
There are only two short-term defenses: First, manually restart the Claude desktop regularly. It is recommended to exit every 1-2 days to reset the fd to zero. Second, if you are a technical user, you can add a cron or launchd monitor and run lsof /dev/ptmx | wc -l. If the threshold is exceeded, a desktop notification will be sent to remind you to restart Claude. Long-term defense and other official fixes.
Can ordinary users manually increase the upper limit of ptmx_max?
Not recommended. sudo sysctl -w kern.tty.ptmx_max=2048 can temporarily raise the upper limit to 2048, but this only changes "collapse in a few days" to "collapse in two weeks" and does not solve the root cause. Moreover, this modification will become invalid after restarting the system. To make it persist, you need to write a plist file. General users do not need to modify kernel parameters for third-party apps.
Will all fd be recycled after exiting Claude?
meeting. This is the standard behavior of POSIX systems. When the process exits, all file descriptors it holds will be automatically closed by the kernel. Run lsof /dev/ptmx | wc -l immediately after exiting Claude and you will see that the number jumps back from 500 to less than 20. If fd is not recycled, it means that there are still processes related to Claude that have not been retired. You can use pgrep -i claude to check the remaining processes.
Will this leak affect the functionality of Claude itself?
Yes, but it is an indirect effect. Claude desktop also needs to apply for a new PTY when running the Claude Code command. When the PTY is completely used up, it will fail to open a new session on its own. Therefore, when the upper limit is reached, not only the terminal cannot be opened, but Claude will also be abnormal when he runs new commands. This is an implicit usability issue for workflows that rely on Claude Code.
📝 本文来自抖文 www.douwen.me ,转载请保留出处。
原文链接:https://www.douwen.me/archives/1206/
💬 评论 (0)
还没有评论,来说两句吧 ✍️