[BUG] Interactive TUI freezes permanently: main thread blocks in openat() during clipboard polling (macOS)
Summary. On a headless macOS machine running several concurrent interactive sessions, the TUI freezes completely. The process stays alive but stops repainting — the spinner and its elapsed-time counter stop advancing — and no keystroke (Esc, Ctrl-C) is accepted. Recovery requires kill -9 and --resume. Seen several times on the same machine; in one case the freeze was confirmed to persist for 15+ minutes across three separate sample runs.
Environment. Claude Code 2.1.280 and 2.1.281, macOS 26.4 (Darwin 25.4.0), Apple silicon, ~10 concurrent CLI instances, all sessions inside tmux with no GUI user logged in.
What the process is doing when frozen. Repeated sample runs, minutes apart, show the main thread parked in the same place with identical call-site addresses:
2208 Thread_xxx DispatchQueue_1: com.apple.main-thread
... JIT frames (JS) -> runtime ->
2208 openat$NOCANCEL (in libsystem_kernel.dylib) + 76
2208 __openat_nocancel (in libsystem_kernel.dylib) + 8
CPU time advances by ~10 ms over 15 minutes (0.0% CPU). The session transcript's last write coincides with the freeze. Two TCP sockets remain open in CLOSED state (leaked fds).
So the render/main thread is sitting in a blocking open() that never returns, and because it is the thread that paints the UI and reads input, the whole session dies with it.
What I have not been able to establish: which path is being opened. Two earlier occurrences had clipboard reads as their last os_log entries before going silent:
[com.apple.AppKit:Pasteboard] NSPasteboard (name: Apple CFPasteboard general): data requested for type public.png at index 0
... same for public.heic / public.heif / public.avif / public.jpeg / org.webmproject.webp / com.compuserve.gif / com.microsoft.bmp / public.tiff
<silence - process never logs again>
I originally read this as evidence that the clipboard poll was the trigger. That attribution does not hold up, and I want to correct it rather than send maintainers down the wrong path:
- the poll runs continuously, so it is the last log line before any freeze, whatever the cause;
- I then kept the system pasteboard empty with a launchd agent (verified: it clears file references within 60 s), and a freeze with the identical stack still occurred afterwards on 2.1.281. So an empty clipboard is not a workaround, and the clipboard is at best one candidate among several.
I also want to be precise about frequency: the occurrences are not all independent. Some of the later ones happened in instances that my own tooling had restarted, so I would not treat the count as a clean rate.
Ruled out as environmental causes. Machine load and memory were fine and other CLI instances kept working normally; tccd responds instantly (protected-path read returns EPERM in 0.01 s); the pasteboard server pbs is alive and pbpaste returns immediately; there are no network or FUSE mounts; the session directories contain no FIFOs; no lock file is held by any instance; sample shows no other thread doing work.
Impact. The session cannot be recovered by the user — the UI accepts no input, so there is no way to interrupt or quit; the only exit is killing the process from outside.
Requests (independent of which path blocks).
- Do not run file opens that can block on the render/main thread, or make them cancellable.
- Give such reads a timeout. An unbounded blocking syscall in the UI loop is unrecoverable from inside the app.
- If the blocking call can be identified, log its path before issuing it — with a stripped binary there is no way for a user to tell which file is involved, which is why this report cannot name it.
- Consider a documented opt-out for machines with no human at the keyboard (I grepped the shipped binary; there is no
CLAUDE_CODE_*CLIPBOARD*-style switch today).
Related: #89272 reports an uninterruptible openat deadlock in headless claude -p and states interactive sessions are unaffected — this report is the interactive case.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗