Description
setupTerminal() never calls enableKittyKeyboard() — Kitty keyboard protocol never negotiated on compliant terminals (WezTerm, etc.)
Summary
On terminals that strictly follow the Kitty Keyboard Protocol spec (notably WezTerm, but also Ghostty and other opt-in implementations), opencode's setupTerminal() sets internal Kitty protocol flags via setKittyKeyboardFlags() but never calls enableKittyKeyboard(), which is the method that sends the negotiate sequence \x1b[=5;1u to the terminal. As a result, the protocol is never activated, and all modifier+Enter combinations (Ctrl+Enter, Shift+Enter, Ctrl+Shift+Enter) send the same legacy byte \r (0x0d) as bare Enter.
This makes input_submit: ctrl+return + input_newline: return (and similar modifier-based configs) impossible to work on compliant terminals.
Root cause
Traced from the binary (v1.18.3):
1. setupTerminal() does NOT call enableKittyKeyboard()
The full method body at JS offset 93759379–93761272:
async setupTerminal(){
if(this._terminalIsSetup) return;
this._terminalIsSetup = !0;
// ... terminal setup, capabilities, mouse, etc. ...
// ❌ NO call to this.enableKittyKeyboard()
}
Confirmed: Neither enableKittyKeyboard nor setKittyKeyboardFlags appears anywhere in setupTerminal().
2. Only setKittyKeyboardFlags() is called — internal state only, no negotiation
At renderer initialization (offset 93728255):
let j = H.useKittyKeyboard ?? {}, // {} from app.tsx config
z = rJ(j); // → flags=5 (GQ|qQ: disambiguate + alternateKeys)
U.setKittyKeyboardFlags(V, z); // stores flags internally, does NOT send to terminal
3. enableKittyKeyboard() exists but has zero call sites
Method definition (offset 93759024):
enableKittyKeyboard($=3){
this.lib.enableKittyKeyboard(this.rendererPtr, $), // sends \x1b[=3;1u to terminal
this.updateStdinParserProtocolContext({kittyKeyboardEnabled: !0})
}
Not a single call site invokes this method across the entire JS bundle. The native enableKittyKeyboard function (signature: {args:["u32","u8"],returns:"void"}) which sends the negotiate sequence \x1b[=flags;modeu is never called.
4. The two native functions are separate
From the FFI bindings:
| Function |
What it does |
setKittyKeyboardFlags(ptr, flags) |
Stores flags in internal renderer state only |
enableKittyKeyboard(ptr, flags) |
Sends \x1b[=flags;modeu to terminal to negotiate protocol |
The JS code calls setKittyKeyboardFlags but never enableKittyKeyboard.
Why it works on Kitty terminal but not WezTerm
- Kitty terminal sends enhanced keyboard sequences (CSI u) by default — no negotiation needed. opencode's parser just happens to receive them and tries to decode them (hence parsing bugs reported in other issues).
- WezTerm (and Ghostty, iTerm2 with strict opt-in mode) strictly follows the Kitty Keyboard Protocol spec: it only sends enhanced sequences after receiving the
\x1b[=flags;modeu negotiate sequence. Since opencode never sends it, WezTerm never activates the protocol.
Impact
On WezTerm (and any terminal that strictly implements opt-in Kitty Keyboard Protocol):
- All
input_submit / input_newline configs using Enter with modifiers don't work
input_submit: ctrl+return → sends \r (same as bare Enter), not distinguishable
input_newline: shift+return → same issue
- The only reliable keybinding for newline is
ctrl+j (sends \n = 0x0a), which is fundamentally different from \r at the byte level
Suggested fix
In setupTerminal(), after terminal capabilities are gathered, add:
this.enableKittyKeyboard();
Or, to match the already-configured flags (5 instead of the default 3):
let flags = this.lib.getKittyKeyboardFlags(this.rendererPtr);
if (flags > 0) this.enableKittyKeyboard(flags);
Environment
- opencode version: 1.18.3
- Terminal: WezTerm 20240203-110809-5046fc22
- WezTerm config:
config.enable_kitty_keyboard = true
- OS: Linux
Plugins
No response
OpenCode version
1.18.3
Steps to reproduce
No response
Screenshot and/or share link
No response
Operating System
Linux
Terminal
Wezterm
Description
setupTerminal()never callsenableKittyKeyboard()— Kitty keyboard protocol never negotiated on compliant terminals (WezTerm, etc.)Summary
On terminals that strictly follow the Kitty Keyboard Protocol spec (notably WezTerm, but also Ghostty and other opt-in implementations), opencode's
setupTerminal()sets internal Kitty protocol flags viasetKittyKeyboardFlags()but never callsenableKittyKeyboard(), which is the method that sends the negotiate sequence\x1b[=5;1uto the terminal. As a result, the protocol is never activated, and all modifier+Enter combinations (Ctrl+Enter, Shift+Enter, Ctrl+Shift+Enter) send the same legacy byte\r(0x0d) as bare Enter.This makes
input_submit: ctrl+return+input_newline: return(and similar modifier-based configs) impossible to work on compliant terminals.Root cause
Traced from the binary (v1.18.3):
1.
setupTerminal()does NOT callenableKittyKeyboard()The full method body at JS offset 93759379–93761272:
Confirmed: Neither
enableKittyKeyboardnorsetKittyKeyboardFlagsappears anywhere insetupTerminal().2. Only
setKittyKeyboardFlags()is called — internal state only, no negotiationAt renderer initialization (offset 93728255):
3.
enableKittyKeyboard()exists but has zero call sitesMethod definition (offset 93759024):
Not a single call site invokes this method across the entire JS bundle. The native
enableKittyKeyboardfunction (signature:{args:["u32","u8"],returns:"void"}) which sends the negotiate sequence\x1b[=flags;modeuis never called.4. The two native functions are separate
From the FFI bindings:
setKittyKeyboardFlags(ptr, flags)enableKittyKeyboard(ptr, flags)\x1b[=flags;modeuto terminal to negotiate protocolThe JS code calls
setKittyKeyboardFlagsbut neverenableKittyKeyboard.Why it works on Kitty terminal but not WezTerm
\x1b[=flags;modeunegotiate sequence. Since opencode never sends it, WezTerm never activates the protocol.Impact
On WezTerm (and any terminal that strictly implements opt-in Kitty Keyboard Protocol):
input_submit/input_newlineconfigs using Enter with modifiers don't workinput_submit: ctrl+return→ sends\r(same as bare Enter), not distinguishableinput_newline: shift+return→ same issuectrl+j(sends\n= 0x0a), which is fundamentally different from\rat the byte levelSuggested fix
In
setupTerminal(), after terminal capabilities are gathered, add:Or, to match the already-configured flags (5 instead of the default 3):
Environment
config.enable_kitty_keyboard = truePlugins
No response
OpenCode version
1.18.3
Steps to reproduce
No response
Screenshot and/or share link
No response
Operating System
Linux
Terminal
Wezterm