Skip to content

fix(minimald): fix detach-chord detection under zellij - #440

Merged
twitchyliquid64 merged 1 commit into
mainfrom
tom/keyboard
Jun 17, 2026
Merged

fix(minimald): fix detach-chord detection under zellij#440
twitchyliquid64 merged 1 commit into
mainfrom
tom/keyboard

Conversation

@twitchyliquid64

@twitchyliquid64 twitchyliquid64 commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Enhanced keyboard input handling to support additional keyboard protocol variants, enabling session detach with Ctrl-W in Kitty and compatible terminals. Improved terminal compatibility for keyboard input recognition.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

In session_host.rs, two const byte-slice patterns are added for the Kitty keyboard protocol representations of ctrl-w (CSI 119;5 u and CSI 27;5;119~). The detach detection branch is updated to match either the raw 0x17 byte or either Kitty CSI sequence, while non-matching input continues to be buffered for the PTY.

Changes

Kitty ctrl-w detach support

Layer / File(s) Summary
Kitty ctrl-w constants and detach detection
crates/minimald/src/session_host.rs
Adds KITTY_CTRL_W_CSI_U and KITTY_CTRL_W_CSI_TILDE byte-slice constants; extends the detach condition to match either Kitty CSI sequence in addition to the raw 0x17 Ctrl-W byte.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

Possibly related PRs

  • gominimal/minimal#431: Also modifies the ctrl-w detach path in session_host.rs, specifically adding terminal unwind/reset payloads sent during detach — directly adjacent to the detection logic changed here.

Poem

🐇 Hop hop, the keys have changed their tune,
Two Kitty sequences joined the room,
Ctrl-W now speaks in CSI,
The session detaches, wave goodbye!
No byte left behind — the rabbit approves. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main fix: enabling detach-chord detection to work under zellij by adding Kitty keyboard protocol support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/minimald/src/session_host.rs`:
- Around line 927-930: The is_detach logic at lines 928-930 performs exact byte
equality checks on individual message chunks, but multi-byte CSI escape
sequences like CTRL_W_CSI_U and CTRL_W_CSI_27 can be fragmented across multiple
russh::ChannelMsg::Data messages, causing detach detection to fail. Replace the
per-chunk equality checks with a rolling buffer or state machine that
accumulates incoming bytes across multiple messages and matches against the
complete sequences. This ensures that even if a Ctrl-W sequence arrives
fragmented, it will still be properly detected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 35f52f40-e407-4a10-8778-3028aab38248

📥 Commits

Reviewing files that changed from the base of the PR and between 01badc9 and 0e20377.

📒 Files selected for processing (1)
  • crates/minimald/src/session_host.rs

Comment on lines +927 to +930
// ctrl-w
let is_detach = b.len() == 1 && b[0] == 0x17 ||
b == CTRL_W_CSI_U ||
b == CTRL_W_CSI_27;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does russh::ChannelMsg::Data guarantee input message boundaries for terminal keystroke streams, or can CSI escape sequences be split/coalesced across messages?

💡 Result:

The russh::ChannelMsg::Data variant does not guarantee input message boundaries for terminal keystroke streams or any other application-level data [1][2]. SSH channel data is treated as a continuous, unstructured byte stream [3][4]. Because the SSH connection protocol (RFC 4254) multiplexes data over channels without preserving application-layer framing, CSI escape sequences and other terminal control codes can be split, coalesced, or fragmented across multiple ChannelMsg::Data messages [3][4]. Key technical considerations: 1. Stream Orientation: The russh library exposes SSH channels as stream-oriented abstractions, such as ChannelStream, which implements AsyncRead and AsyncWrite [1][5]. These abstractions behave like standard TCP-like streams where the receiver must handle partial reads and potentially reassemble data fragments [1][5]. 2. Protocol Nature: SSH SSH_MSG_CHANNEL_DATA packets are designed to carry arbitrary byte streams [4][6]. There is no inherent protocol-level requirement for an SSH message to contain a "complete" sequence of terminal data, and implementations (both client and server) are free to segment data based on window sizes, buffer limits, or network packet boundaries [4]. 3. Handling Requirements: Applications consuming these streams must implement their own buffering and parsing logic to reliably identify and process complete CSI sequences (e.g., waiting until a full ESC [ sequence is received) [3]. You cannot rely on individual ChannelMsg::Data messages to contain atomically complete terminal escape codes [3][4].

Citations:


🏁 Script executed:

# Get context around lines 927-930 in the file
head -n 950 crates/minimald/src/session_host.rs | tail -n 50 | cat -n

Repository: gominimal/minimal

Length of output: 2838


🏁 Script executed:

# Get more context to understand the data flow and any existing buffering
sed -n '900,960p' crates/minimald/src/session_host.rs | cat -n

Repository: gominimal/minimal

Length of output: 3498


🏁 Script executed:

# Search for existing buffering logic or state machines in the file
rg -n "buffer|Buffer|state|State" crates/minimald/src/session_host.rs | head -20

Repository: gominimal/minimal

Length of output: 715


🏁 Script executed:

# Check the function/method containing this code to understand the full context
sed -n '850,950p' crates/minimald/src/session_host.rs | cat -n

Repository: gominimal/minimal

Length of output: 5439


🏁 Script executed:

# Find where remote_rx is defined and initialized
rg -n "remote_rx" crates/minimald/src/session_host.rs | head -20

Repository: gominimal/minimal

Length of output: 302


🏁 Script executed:

# Search for where remote_rx gets its data (who sends to it)
rg -n "remote_rx\|Binding::run" crates/minimald/src/ -A 5 | grep -E "(fn run|send|remote_rx)" | head -30

Repository: gominimal/minimal

Length of output: 43


🏁 Script executed:

# Find the Binding struct and run method to understand data flow
fd -t f "*.rs" crates/minimald/src/ | xargs rg -l "Binding" | head -5

Repository: gominimal/minimal

Length of output: 399


🏁 Script executed:

# Search for where remote_tx is used (who sends data)
rg -n "remote_tx" crates/minimald/src/ -A 2 -B 2

Repository: gominimal/minimal

Length of output: 1974


🏁 Script executed:

# Find Binding::run to understand data flow from russh
rg -n "impl.*Binding|fn run" crates/minimald/src/session_host.rs | head -20

Repository: gominimal/minimal

Length of output: 113


🏁 Script executed:

# Look for the actual file that contains Binding::run or the connection handler
find crates/minimald/src -name "*.rs" -exec grep -l "Binding.*run\|remote_tx.send" {} \;

Repository: gominimal/minimal

Length of output: 43


🏁 Script executed:

# Read the Binding::run method to understand data flow from russh
sed -n '236,350p' crates/minimald/src/session_host.rs | cat -n

Repository: gominimal/minimal

Length of output: 6213


🏁 Script executed:

# Also find the Binding struct definition
sed -n '218,240p' crates/minimald/src/session_host.rs | cat -n

Repository: gominimal/minimal

Length of output: 941


🏁 Script executed:

# Search for where data is sent via remote_tx in Binding::run
sed -n '236,400p' crates/minimald/src/session_host.rs | grep -n "remote_tx\|send\|Channel"

Repository: gominimal/minimal

Length of output: 516


Detach detection logic depends on chunk boundaries and can miss Ctrl-W sequences.

At lines 928–930, is_detach checks exact equality against incoming bytes chunks. Since russh::ChannelMsg::Data does not guarantee message boundaries, CSI escape sequences (like CTRL_W_CSI_U) can be split across multiple messages. If a detach sequence fragments, neither chunk will match and detach will fail to trigger.

Implement stream-aware matching using a small rolling buffer or state machine instead of per-chunk equality checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimald/src/session_host.rs` around lines 927 - 930, The is_detach
logic at lines 928-930 performs exact byte equality checks on individual message
chunks, but multi-byte CSI escape sequences like CTRL_W_CSI_U and CTRL_W_CSI_27
can be fragmented across multiple russh::ChannelMsg::Data messages, causing
detach detection to fail. Replace the per-chunk equality checks with a rolling
buffer or state machine that accumulates incoming bytes across multiple messages
and matches against the complete sequences. This ensures that even if a Ctrl-W
sequence arrives fragmented, it will still be properly detected.

@twitchyliquid64
twitchyliquid64 enabled auto-merge (rebase) June 17, 2026 17:11
@twitchyliquid64
twitchyliquid64 merged commit b3f42b0 into main Jun 17, 2026
35 of 41 checks passed
@twitchyliquid64
twitchyliquid64 deleted the tom/keyboard branch June 17, 2026 17:15
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.

2 participants