fix(tui): restore Ctrl+\ detach when focusing sessions with enhanced keyboard modes#19
fix(tui): restore Ctrl+\ detach when focusing sessions with enhanced keyboard modes#19jakemassoth wants to merge 1 commit into
Conversation
…keyboard modes Focusing the pinned head-chef entry (Claude Code) left Ctrl+\ unable to detach: the user was stuck in the session. Root cause is not nesting, process groups, or crossterm — it's the terminal keyboard protocol. Claude Code enables xterm modifyOtherKeys (CSI > 4 ; 2 m). zmx faithfully replays the session's saved terminal modes to the client on attach, so the mode is active the instant we attach. Under modifyOtherKeys the terminal encodes Ctrl+\ as the escape sequence CSI 27;5;92~ rather than the raw 0x1C byte that zmx's detach watches for, so zmx never detaches. An idle line cook (no such mode) detaches fine, which is why it looked intermittent. Mitigation (keeps the zmx-attach shell-out): after zmx finishes replaying the session state, disable modifyOtherKeys and pop kitty keyboard flags so Ctrl+\ reaches zmx as raw 0x1C again. Applied to both attach() and attach_raw() via run_attach_restoring_detach(). Tradeoff: the focused app loses enhanced key disambiguation until re-attach. The clean fix belongs in zmx (recognize the encoded Ctrl+\, or don't replay input-only keyboard modes to the client). Verified with a PTY harness that simulates a modifyOtherKeys terminal (encodes Ctrl+\ as Ghostty would): reproduced focused=1 detached=0 before, focused=1 detached=1 after; plain cooks and 'yeschef attach' still detach. cargo fmt/clippy(pedantic)/test and the e2e suite all pass.
|
Closing — this one-shot reset is not an adequate fix, confirmed against a real terminal. Real root cause: zmx bug, not yeschefThis is upstream zmx issue neurosnap/zmx#124 — "ctrl+\ detach not detected when inner program enables xterm modifyOtherKeys." zmx's Why the one-shot reset here is inadequateIt disables modifyOtherKeys ~200ms after attach, so the first Ctrl+\ detaches — but Claude Code re-asserts modifyOtherKeys on the next keystroke, so detach breaks again the moment you type. Any one-shot reset loses to the app re-enabling the mode. Correct fixAdopt the zmx-side fix (PR #125): bump the pinned zmx once it merges, or pin yeschef's The DEVELOPMENT.md note about this mechanism is worth keeping regardless; I'll carry it into whatever lands. |
The bug
Focusing the pinned head-chef entry in the TUI (
C→ Enter, whichzmx attaches theheadchefClaude Code session) left Ctrl+\ unable to detach — the user was stuck in the session.yeschef attach-ing that session had the same problem.Root cause (not nesting / process groups / crossterm — it's the keyboard protocol)
I built PTY harnesses to isolate this and proved the chain end-to-end:
0x1C(Ctrl+\). It does not detach on the escape-sequence form.CSI > 4 ; 2 m) — confirmed from the realheadchefsession's terminal stream.zmx attachreplays the session's saved terminal modes to the client on attach (verified) — so modifyOtherKeys is active the instant you attach.CSI 27 ; 5 ; 92 ~, not0x1C. zmx never sees its detach byte → no detach.An idle line cook has no such mode, so it detaches fine — which is why the bug looked intermittent / hard to pin down. It is not nested and does not depend on process groups, signal disposition, or leftover crossterm state (all ruled out empirically).
The fix
After
zmx attachfinishes replaying the session state, disable the enhanced keyboard modes (CSI > 4 ; 0 mto turn off modifyOtherKeys;CSI < uto pop kitty keyboard flags) so Ctrl+\ reaches zmx as raw0x1Cagain. Factored intorun_attach_restoring_detach()and applied to bothattach()andattach_raw(). Keeps thezmx attachshell-out.Tradeoff: while focused, the app loses enhanced key disambiguation (e.g. it can't tell
Shift+EnterfromEnter) until you leave and re-attach — an acceptable price for being able to detach at all.Proper long-term fix belongs in zmx: its detach handler should also recognize the encoded Ctrl+\ (
CSI 27;5;92~), or it should not replay input-only keyboard modes to the client. That would fix it without the tradeoff. Left as a follow-up in a separate repo.Verification
CSI>4;Nmfrom the stream and encodes Ctrl+\ the way Ghostty would): driving the realyeschef tuifocus flow against a modifyOtherKeys cook gavefocused=1 detached=0before the fix andfocused=1 detached=1after.yeschef attachon a plain cook both still detach cleanly.cargo fmt --check,cargo clippy --all-targets -- -D warnings -D clippy::pedantic,cargo test --bin yeschef(59), and the e2e suite (16) all pass.