Bug
A single Claude tab stopped accepting all keyboard input (typed characters, Enter, arrows, Cmd+V paste) while its terminal kept rendering output normally. Every other tab in the same window worked. The tab had been restored at app launch (session resume) and worked for ~75 minutes before freezing; the freeze began right after the Claude session displayed an AskUserQuestion prompt and went idle.
Evidence gathered (live, before the repro was lost)
Instrumented externally (PTY inspection via lsof/FIONREAD) and internally (lldb attach reading live object state):
- The tab's
claude process was healthy: idle in kevent64, MCP servers up, valid PTY, raw-mode termios. Zero bytes of stdin/stdout activity from the freeze onward.
- Keystrokes never reached the kernel: 0 pending bytes in the slave input queue (
FIONREAD), no I/O counter movement on the process during supervised typing/paste tests, and no bytes cross-wired into any of the other 44 tabs' PTYs.
- Deckard's read side of the same PTY stayed fully alive: a 64KB probe written to the slave drained instantly (rendered fine).
- Live app state at the moment the tab was focused (via lldb): first responder was the tab's own
DeckardTerminalView, TerminalSurface.keyEventMonitor == nil (no leaked monitor), handlesPasteShortcuts == true, pendingInitialInput == nil, no stuck marked text (IME), kitty keyboard flags empty, LocalProcess.running == true, and childfd verified (device minor match) as the correct PTY master.
So input vanished somewhere between AppKit event dispatch and the write() to the PTY master, with every inspectable layer healthy.
Leading theory
SwiftTerm.LocalProcess.send() uses fire-and-forget DispatchIO.write(toFileDescriptor:) per keystroke, with errors only printed (stdout is /dev/null for the app). A wedged libdispatch write path for that one fd would black-hole all input while reads (a separate persistent DispatchIO channel) keep working — matching every observation, including that no object state showed anything wrong. The app instance was also burning ~240% CPU on average, consistent with something spinning. Could not be confirmed: the app instance died (debugger mishap) before a breakpoint trace ran, and the fresh instance resumed the session with working input.
Expected
Keystroke writes to the PTY either succeed or fail loudly (surfaced error, tab marked broken) — never silently vanish while the tab looks alive.
Proposed fixes
- SwiftTerm fork: replace per-call
DispatchIO.write(toFileDescriptor:) in LocalProcess.send() with a persistent write channel (or serial-queue write() loop) and surface write errors to the delegate.
- Related latent bug found while auditing (separate from this freeze — state inspection proved it wasn't the cause here, but it's real): in
TerminalSurface.startShell, the +0.3s closure early-returns when pendingInitialInput is nil before removing the window-wide key-swallowing keyEventMonitor; removal should be in the defer.
Bug
A single Claude tab stopped accepting all keyboard input (typed characters, Enter, arrows, Cmd+V paste) while its terminal kept rendering output normally. Every other tab in the same window worked. The tab had been restored at app launch (session resume) and worked for ~75 minutes before freezing; the freeze began right after the Claude session displayed an AskUserQuestion prompt and went idle.
Evidence gathered (live, before the repro was lost)
Instrumented externally (PTY inspection via lsof/FIONREAD) and internally (lldb attach reading live object state):
claudeprocess was healthy: idle inkevent64, MCP servers up, valid PTY, raw-mode termios. Zero bytes of stdin/stdout activity from the freeze onward.FIONREAD), no I/O counter movement on the process during supervised typing/paste tests, and no bytes cross-wired into any of the other 44 tabs' PTYs.DeckardTerminalView,TerminalSurface.keyEventMonitor == nil(no leaked monitor),handlesPasteShortcuts == true,pendingInitialInput == nil, no stuck marked text (IME), kitty keyboard flags empty,LocalProcess.running == true, andchildfdverified (device minor match) as the correct PTY master.So input vanished somewhere between AppKit event dispatch and the
write()to the PTY master, with every inspectable layer healthy.Leading theory
SwiftTerm.LocalProcess.send()uses fire-and-forgetDispatchIO.write(toFileDescriptor:)per keystroke, with errors onlyprinted (stdout is /dev/null for the app). A wedged libdispatch write path for that one fd would black-hole all input while reads (a separate persistentDispatchIOchannel) keep working — matching every observation, including that no object state showed anything wrong. The app instance was also burning ~240% CPU on average, consistent with something spinning. Could not be confirmed: the app instance died (debugger mishap) before a breakpoint trace ran, and the fresh instance resumed the session with working input.Expected
Keystroke writes to the PTY either succeed or fail loudly (surfaced error, tab marked broken) — never silently vanish while the tab looks alive.
Proposed fixes
DispatchIO.write(toFileDescriptor:)inLocalProcess.send()with a persistent write channel (or serial-queuewrite()loop) and surface write errors to the delegate.TerminalSurface.startShell, the +0.3s closure early-returns whenpendingInitialInputis nil before removing the window-wide key-swallowingkeyEventMonitor; removal should be in thedefer.