Claude Desktop PTY Leak on macOS: 490 /dev/ptmx FDs After 4 Days of Use

📅 2026-05-26 12:06:14 👤 DouWen Editorial 💬 0 条评论 👁 47

This note documents a system-level PTY file descriptor leak triggered after the Claude desktop app runs for a long time. The surface symptom is that all terminal apps on macOS suddenly fail to open new windows; digging deeper, you can pinpoint that the Claude process accumulated close to 490 /dev/ptmx file descriptors over the past few days, eating up almost all the PTYs the entire system can use. This article unfolds in the order "discovery → diagnosis → root cause → reproduction → temporary workaround → suggestions for the fixers," to help other developers facing similar situations locate the issue quickly, and to help the upstream team review and fix it.

1. Symptom: All Terminal Apps Suddenly Won't Open

Illustration

It happened on a macOS laptop in daily use. The system had been running stably for several days, with the Claude desktop app kept open the whole time for daily work, during which I had also run many sessions in the terminal with Claude Code.

One afternoon I wanted to open a new iTerm window to run git status. After iTerm launched, only a single line of error flashed in the window before it exited, reading forkpty: Device not configured. Switching to Terminal.app gave the same error. VS Code's built-in terminal, tmux, and screen all failed to open new sessions, all with the same Device not configured.

Commands still ran fine in already-running terminal windows, but any attempt to fork a new pseudo-terminal failed immediately. This symptom of "existing processes are fine, but all new PTYs fail" is very characteristic and points to PTY resource exhaustion.

2. Diagnosis: First Check the sysctl Limit, Then Check Usage

Illustration

On macOS the total number of PTYs has a kernel limit, which you can read directly via sysctl.

sysctl kern.tty.ptmx_max
kern.tty.ptmx_max: 511

The system allows only 511 PTYs to exist simultaneously in total. This value is a hardcoded small number on macOS, in a completely different order of magnitude from Linux's /proc/sys/kernel/pty/max, which is often in the tens of thousands. Once I realized this, the next step was to see who had filled it up.

/dev/ptmx is the entry point for requesting a new PTY; each open allocates a new master/slave pair. So counting which processes hold file descriptors on /dev/ptmx pinpoints the source of usage.

lsof /dev/ptmx | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn

When the result came out, the number one was the Claude process, with the count hovering around 490. All other processes combined came to fewer than 20. In other words, the Claude desktop app alone exhausted the PTY resources.

 490 Claude
   6 iTerm2
   3 tmux
   1 sshd
   ...

At this point I could basically confirm: in some usage scenario, the Claude desktop app opened PTYs but did not close them, and when the session ended the PTY handles were not released either, accumulating day after day until they approached the limit.

3. Root Cause: PTY Descriptors Not Reclaimed

Illustration

Continuing to expand lsof to see the details of the PTYs Claude held.

lsof -p <claude_pid> | grep ptmx | head

Every line in the output is an independent descriptor for /dev/ptmx, with FD numbers climbing from the dozens all the way to the hundreds, and these PTYs all appear to be in an "orphaned" state. They are no longer associated with any active Claude Code session or terminal window, yet the handles are still hanging on the Claude main process or its helper child processes, never closed.

The most likely code-level cause is that when the Claude desktop app internally launches Claude Code or other subtasks needing a pseudo-terminal, it requests a PTY via forkpty or posix_openpt, but does not correctly close the master-side descriptor after the task ends. Each run of a relevant task leaks one, and the more it runs, the worse the accumulation.

From the application-layer perspective, Claude users have no idea that every time they run a Claude Code command, a PTY is opened at the system layer behind the scenes, much less imagine that these PTYs will become a long-term leaked resource.

4. Reproduction Conditions: Long Runtime + Frequent Claude Code Sessions

This bug will not reproduce within five minutes; it requires time to accumulate. Based on observations on this machine, the typical trigger path is as follows.

First, the Claude desktop app runs continuously for many days without quitting, which is the norm for moderate-intensity users; many people go more than a week between Mac restarts.

Second, frequent use of Claude Code during this period, including opening sessions inside the Claude app, launching Claude Code commands in an external terminal, and running batch tasks in headless mode. Each additional trigger scenario raises the frequency of PTY requests.

Third, other system apps occasionally use PTYs too, such as iTerm, Cursor's built-in terminal, and remote ssh; these stack on top of Claude's leak amount, accelerating hitting bottom.

After the first two conditions are met, it takes about 3–5 days to hit the 511 limit. At the time of this report on this machine, lsof showed Claude holding 490 PTYs, with other processes using about 15, for a total of 505, leaving only 6 from the 511 limit, so opening a new terminal basically always fails.

5. Environment Info: Precise Version Numbers

To make it easier for the upstream team to cross-check, here are the key versions of this machine.

The operating system is macOS 15.7.3. Apple Silicon platform. The Claude desktop app version is per the "About" page in the app menu; at the time of this report it was the latest version on the current stable channel. The Claude Code CLI likewise uses the latest version, either built into the app or installed standalone.

The reproduced sysctl value is macOS 15's default configuration, with no manual adjustment of kern.tty.ptmx_max; this value has stayed at 511 across the most recent few macOS versions, and this machine also has no custom limit set via launchctl.

6. Temporary Workaround: Restart Claude to Release Immediately

After pinpointing the root cause, the workaround is very direct. Fully quit the Claude desktop app (including Quit Claude from the menu bar, not just closing the main window); once all Claude processes exit, the system will automatically reclaim all the PTY descriptors it held.

Verifying immediately after the operation, running lsof /dev/ptmx again showed that the Claude line had disappeared and the total had dropped back to around 20. Opening iTerm, everything returned to normal, and new terminals could fork properly.

But this workaround has an obvious cost. Quitting Claude means losing all the in-progress conversation context, and after reopening, the previous Claude Code working sessions also need to be rebuilt. For users who treat Claude as their primary work environment, it is almost equivalent to a workflow interruption.

A more interesting phenomenon is that as long as you do not quit Claude, even if you have used Activity Monitor to kill all terminal apps like iTerm and Terminal, new terminals still will not open. That is because the PTY handles are held by Claude, not by the terminal apps.

7. Suggested Directions for the Fixers

At the application code level, the possible fix directions are these.

First, wrap all forkpty or posix_openpt calls in RAII wrappers with explicit close. At the several exit points—child process ending, stream receiving EOF, user interrupting the session—you must ensure the master-side fd goes through close. If the Node.js side uses node-pty, confirm that after .destroy() or .kill() the fd is actually closed, not just the child process terminated.

Second, add a watchdog. The Claude main process can periodically self-check the number of /dev/ptmx fds it holds; once it exceeds a threshold (say 50 or 100), log a warning to make it easy for telemetry to collect the real-world hit rate. If it exceeds a higher threshold (say 200), proactively close old idle PTYs.

Third, give users a manual cleanup entry. Add a "Release unused PTYs" button in the settings or debug panel, so that when users notice a problem with the system terminals, they can click this first without quitting Claude entirely.

Fourth, check the lifecycle management of helper child processes. On macOS the Claude desktop app uses multiple Helper processes; if the leak occurs in a Helper and the Helper is continuously retained by the main process, the fds get locked along with the Helper. Properly reclaiming Helpers when they should be reclaimed would solve a large part of this.

8. System-Level Notes for Developers

On macOS, kern.tty.ptmx_max=511 is a fairly tight limit, in a completely different order of magnitude from Linux. Any app on macOS that needs to launch pseudo-terminals in bulk must design around this number as a hard limit.

In theory macOS allows the root user to temporarily raise this value via sysctl, for example sudo sysctl -w kern.tty.ptmx_max=2048, but this is not a recommendation aimed at end users. Ordinary users have no need to modify a system kernel parameter for the sake of a third-party app, and should not be advised to do so.

The Claude desktop app is positioned as a product for all macOS users, and by default it should run safely within the 511 limit. A long-running app holding hundreds of PTYs is itself a design defect worth fixing.

9. Impact Assessment: Not Just Terminals Failing to Open

PTY exhaustion does not just affect the one scenario of "opening new terminal apps"; it affects many workflows that depend on pseudo-terminals in subtle ways.

Some npm scripts in build tools use PTYs to run subcommands, and in a PTY-exhausted state they fail with errors that are very hard to diagnose. Docker Desktop may also have issues when launching new containers if it uses PTYs. SSH remote login to this machine, or ssh from this machine to a remote, will both fail, because ssh allocates a pseudo-terminal by default.

Worse, these failures do not point to Claude. Users will only see "my npm install suddenly won't install" or "my Docker failed to start," and would never imagine the root cause is Claude holding 490 PTYs in the background. This kind of "indirect harm" makes attributing the bug report very difficult.

10. A Self-Check Checklist for People Facing the Same Problem

If you are reading this article because you ran into the same "terminal suddenly won't open" problem, the self-check checklist below can quickly determine whether it is the same bug.

Step one, run sysctl kern.tty.ptmx_max and confirm that the macOS limit is around 511, not one you raised yourself.

Step two, run lsof /dev/ptmx | wc -l; if the number is close to or exceeds 500, the PTYs are basically exhausted.

Step three, run lsof /dev/ptmx | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn to see who is the number one user.

Step four, if the number one is Claude and the count exceeds 100, it is basically this article's bug. Fully quit the Claude desktop app, then immediately open a new terminal to test whether it recovers.

Step five, if you are willing to provide data to the upstream fixers, before quitting, capture a snapshot of the current fd list with lsof -p <claude_pid> | grep ptmx > /tmp/claude_pty_leak.txt and attach it when filing the issue.

Frequently Asked Questions (FAQ)

Does this bug only occur on macOS

The reproductions observed so far are all on macOS, because macOS's PTY limit is only 511, making it very easy to hit the ceiling. On Linux, /proc/sys/kernel/pty/max usually starts at 4096, so even with the same leak rate, the trigger time is pushed out to be imperceptible. But it is essentially the same leak problem; Linux users just do not notice it. Windows has no POSIX PTY; the Claude desktop app uses ConPTY on Windows, and the leak path is different, requiring separate investigation.

Can ordinary users prevent it in advance

Short-term defense has only two options: one is to manually restart the Claude desktop app regularly, recommended once every 1–2 days to reset the fds to zero. Two, if you are a technical user, you can add a cron or launchd monitor that runs lsof /dev/ptmx | wc -l and sends a desktop notification reminding you to restart Claude when it exceeds a threshold. Long-term defense awaits an official fix.

Can ordinary users manually raise the ptmx_max limit

Not recommended. sudo sysctl -w kern.tty.ptmx_max=2048 can temporarily raise the limit to 2048, but this only turns "crashes in a few days" into "crashes in two weeks," not solving the root cause. Moreover, this change is lost after a system restart, and persisting it requires writing a plist file. Ordinary users have no need to modify a kernel parameter for the sake of a third-party app.

Will the fds really all be reclaimed after quitting Claude

Yes. This is standard behavior of POSIX systems: when a process exits, all the file descriptors it held are automatically closed by the kernel. Right after quitting Claude, running lsof /dev/ptmx | wc -l shows the number jump back from 500 to under 20. If the fds are not reclaimed, that means some Claude-related process has not fully exited, and you can check for residual processes with pgrep -i claude.

Will this leak affect Claude's own functionality

Yes, but indirectly. When the Claude desktop app runs a Claude Code command, it likewise needs to request a new PTY, and when the PTYs are all used up, opening a new session itself also fails. So after hitting the limit, not only do terminals fail to open, but Claude itself also misbehaves when running new commands. This is an implicit availability problem for workflows that depend on Claude Code.

📝 本文来自抖文 www.douwen.me ,转载请保留出处。

💬 评论 (0)

还没有评论,来说两句吧 ✍️