Skip to content

[Windows] Attach client freezes (blocking WriteConsoleW on a single-threaded runtime) under output backpressure — e.g. selecting/copying in a TUI app #69

Description

@paceaitian

Summary

On Windows (Windows Terminal + ConPTY), the rmux attach client can freeze completely — the pane stops updating and the keyboard goes dead — while the server stays fully healthy. It is triggered by anything that pauses the terminal's consumption of the ConPTY output pipe; the most reliable trigger is selecting text to copy in a full-screen TUI app (e.g. Claude Code) running inside a pane. In practice the freeze does not recover by clearing the selection, pressing keys, or resizing the window — and even closing the terminal and re-attaching to the same session immediately re-freezes (the pane keeps emitting output that re-blocks the client).

Environment

  • rmux 0.7.1
  • Windows 11 (10.0.26200), Windows Terminal, ConPTY
  • pane shell: PowerShell 7
  • inner app: Claude Code CLI (alt-screen + synchronized output / DECSET 2026, "no-flicker" rendering)

Symptoms

  • Inside a pane running a TUI app, selecting content + copying freezes that pane.
  • The rmux server and other sessions stay fully responsive (list-sessions, capture-pane return in tens of ms even under load).
  • The frozen attach client sits at ~0% CPU (blocked in a syscall, not spinning).
  • Does not reproduce in a detached session (no client → no console writes).

Root cause (source-level)

The Windows attach client runs read-frames / write-console / forward-keys / resize / detach all on a single current-thread Tokio runtime, and the console write is an inline, synchronous, blocking Win32 call:

  • single-thread runtime: crates/rmux-ipc/src/stream_windows.rs:107 (Builder::new_current_thread()), driven by runtime.block_on(drive_async_attach(...))crates/rmux-client/src/attach_windows.rs:216.
  • each loop iteration drains + writes before the select!: crates/rmux-client/src/attach_windows/stream.rs:224-225 (output.write_all(&bytes) + output.flush()).
  • the write is a blocking WriteConsoleW loop until everything is written: crates/rmux-client/src/attach_windows/output.rs:132-162 (Utf16ConsoleWriter::write_text). No spawn_blocking, no dedicated writer thread, no timeout.

So if WriteConsoleW blocks (the terminal stops draining the pipe), the single thread is stuck in the syscall and the runtime cannot advance any other future: it stops reading server frames; the keyboard channel (capacity 256) fills and the keyboard thread's blocking_send blocks → keyboard dead; resize/detach stop. The pane is fully frozen while the server (a separate process) is untouched.

Trigger

Windows consoles pause output while text is selected ("QuickEdit" / selection-pauses-output; cf. microsoft/terminal #34, #4399, #980). That backpressure blocks the client's inline WriteConsoleW.

Aggravating factors:

  1. ENABLE_QUICK_EDIT_MODE is only cleared when the app enables mouse reporting, and left armed otherwise: crates/rmux-client/src/attach_windows/terminal.rs:598-610 (the else branch at :607-609 does not clear it). A TUI app that toggles mouse reporting off (to let the user select natively) re-arms QuickEdit.
  2. rmux answers DECRQM for mode 2026 as "supported" (crates/rmux-core/src/input/dispatch.rs:324-366), and this reply is hardcoded / not gated by terminal-features. That makes apps enable synchronized output and emit full-screen frames; if the client blocks between ?2026h and ?2026l, the terminal stays in an unfinished synchronized batch and the screen looks completely dead (rmux itself warns about this in crates/rmux-core/src/dec_modes.rs:72-75). There is currently no way to disable the 2026 advertisement from config.

What it is NOT

  • Not unanswered terminal queries: the server answers DSR / DA1 / DA2 / DECRQM / DECRQSS / XDA correctly, independent of attach (crates/rmux-core/src/input/dispatch.rs, replies written back via crates/rmux-server/src/pane_io/reader.rs:467).
  • Not clipboard config (set-clipboard, copy-command).

This is the same class as the already-fixed server-side single-thread starvation (#34), but moved to the Windows attach client and worse: a hard blocking syscall rather than CPU starvation.

Reproduction

Requires a real console + interactive selection (cannot be scripted — a non-interactive spawned client gets a pipe, not a console, and never reaches the blocking WriteConsoleW):

  1. Windows Terminal → rmux new -s t → run a full-screen TUI app that uses synchronized output (e.g. Claude Code).
  2. While the app is actively rendering, select text in the terminal (mouse drag, or the app's own copy that toggles mouse mode).
  3. The pane freezes; the server stays responsive. Clearing the selection / resizing / closing + re-attaching does not reliably recover.

Suggested fix

  1. Move console writes off the event-loop thread: wrap WriteConsoleW in tokio::task::spawn_blocking, or use a dedicated writer thread + bounded channel, or run the attach client on a multi-threaded runtime. (Primary fix — same root as Daemon freezes when one pane produces heavy output (single-threaded tokio runtime on Unix) #34.)
  2. Unconditionally clear ENABLE_QUICK_EDIT_MODE in raw_input_mode_with_mouse, not only when the app enables mouse reporting.
  3. (Optional) Gate the DECRQM-2026 "supported" reply behind a config / terminal-features option so synchronized output can be disabled server-side as a mitigation.

Key source references

crates/rmux-ipc/src/stream_windows.rs:107 · crates/rmux-client/src/attach_windows.rs:216 · crates/rmux-client/src/attach_windows/stream.rs:224-225 · crates/rmux-client/src/attach_windows/output.rs:132-162 · crates/rmux-client/src/attach_windows/terminal.rs:598-610 · crates/rmux-core/src/input/dispatch.rs:324-366 · crates/rmux-core/src/dec_modes.rs:72-75

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions