Skip to content

setupTerminal() never calls enableKittyKeyboard() — Kitty protocol unusable on WezTerm #37692

Description

@icoding2016

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

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