Skip to content

fix(tui): restore Ctrl+\ detach when focusing sessions with enhanced keyboard modes#19

Closed
jakemassoth wants to merge 1 commit into
mainfrom
fix-tui-detach
Closed

fix(tui): restore Ctrl+\ detach when focusing sessions with enhanced keyboard modes#19
jakemassoth wants to merge 1 commit into
mainfrom
fix-tui-detach

Conversation

@jakemassoth

Copy link
Copy Markdown
Owner

The bug

Focusing the pinned head-chef entry in the TUI (C → Enter, which zmx attaches the headchef Claude 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:

  1. zmx's detach is byte-based — it detaches on the raw byte 0x1C (Ctrl+\). It does not detach on the escape-sequence form.
  2. Claude Code enables xterm modifyOtherKeys (CSI > 4 ; 2 m) — confirmed from the real headchef session's terminal stream.
  3. zmx attach replays the session's saved terminal modes to the client on attach (verified) — so modifyOtherKeys is active the instant you attach.
  4. Under modifyOtherKeys, the terminal (e.g. Ghostty) encodes Ctrl+\ as CSI 27 ; 5 ; 92 ~, not 0x1C. 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 attach finishes replaying the session state, disable the enhanced keyboard modes (CSI > 4 ; 0 m to turn off modifyOtherKeys; CSI < u to pop kitty keyboard flags) so Ctrl+\ reaches zmx as raw 0x1C again. Factored into run_attach_restoring_detach() and applied to both attach() and attach_raw(). Keeps the zmx attach shell-out.

Tradeoff: while focused, the app loses enhanced key disambiguation (e.g. it can't tell Shift+Enter from Enter) 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

  • PTY harness that simulates a modifyOtherKeys terminal (tracks CSI>4;Nm from the stream and encodes Ctrl+\ the way Ghostty would): driving the real yeschef tui focus flow against a modifyOtherKeys cook gave focused=1 detached=0 before the fix and focused=1 detached=1 after.
  • Regression: focusing a plain cook and yeschef attach on 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.

…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.
@jakemassoth

Copy link
Copy Markdown
Owner Author

Closing — this one-shot reset is not an adequate fix, confirmed against a real terminal.

Real root cause: zmx bug, not yeschef

This is upstream zmx issue neurosnap/zmx#124 — "ctrl+\ detach not detected when inner program enables xterm modifyOtherKeys." zmx's isCtrlBackslash matches raw 0x1C and the kitty CSI-u form (PR #92) but not the modifyOtherKeys form \x1b[27;5;92~. It specifically affects Claude Code CLI, which every yeschef session runs. The upstream fix is zmx PR #125 (open).

Why the one-shot reset here is inadequate

It 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 fix

Adopt the zmx-side fix (PR #125): bump the pinned zmx once it merges, or pin yeschef's zmx-flake to the fix branch. No yeschef Rust change needed. Configuring Claude Code to not use modifyOtherKeys is not an option (unsupported; would break Shift+Enter multiline — anthropics/claude-code#18135 "not planned").

The DEVELOPMENT.md note about this mechanism is worth keeping regardless; I'll carry it into whatever lands.

@jakemassoth jakemassoth closed this Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant