modifyOtherKeys is only enabled for kitty-protocol/allowlisted terminals, so ctrl+enter can't be bound on xterm/VTE
What's wrong
Claude Code only enables xterm's modifyOtherKeys (CSI > 4 ; 2 m) as part of
its "extended keys" mode, and that mode is turned on only when either:
- the detected terminal is in a hardcoded allowlist
(iTerm.app, kitty, WezTerm, ghostty, tmux, windows-terminal,
WarpTerminal), or
- the terminal replies to the kitty keyboard protocol query
CSI ? u.
There is no way for a terminal that implements modifyOtherKeys but not the
kitty keyboard protocol (xterm itself, or a VTE build with modifyOtherKeys
support) to get it enabled. As a result, Ctrl+Enter / Shift+Enter arrive as a
plain CR there, and a binding like
{ "context": "Chat", "bindings": { "ctrl+enter": "chat:newline" } }
never fires: Ctrl+Enter submits the message instead.
The parsing side already works. Launching with TERM_PROGRAM=WezTerm claude
(to get onto the allowlist) makes Claude Code send CSI > 4 ; 2 m, and
Ctrl+Enter then inserts a newline as configured. So only the enablement gating
is missing.
Environment
- Claude Code 2.1.281, Linux (Fedora)
- VTE-based terminal (
VTE_VERSION=8401) patched to support xterm
modifyOtherKeys; no kitty keyboard protocol support
TERM=xterm-256color,TERM_PROGRAMunset (detected asvte-based)
Steps to reproduce
- Use a terminal that supports modifyOtherKeys but does not answer
CSI ? u
(e.g. xterm).
- Add
"ctrl+enter": "chat:newline"to theChatcontext in
~/.claude/keybindings.json.
- Run
claude, type some text, press Ctrl+Enter.
Expected: newline is inserted.
Actual: the message is submitted.
With TERM_PROGRAM=WezTerm claude the same steps give the expected result.
Suggested fix
Decouple modifyOtherKeys from the kitty keyboard protocol, for example:
- probe modifyOtherKeys separately with XTQMODKEYS (
CSI ? 4 m) and enable
CSI > 4 ; 2 m when the terminal answers, and/or
- add an env var / setting to force it on (similar to
CLAUDE_CODE_FORCE_SYNC_OUTPUT), and/or
- enable it for
xterm(XTERM_VERSIONset) by default.
Related: #83491 (same request for xterm, auto-closed as stale).
3 Comments
This is not an enhancement, this is a bug. The terminal supports
modifyOtherKeysbut it is never queried. As a result I can't use Ctrl-Enter which is PITA.The fix is trivial.
Some more details on where this happens (from the shipped 2.1.286 bundle, so
the identifiers below are minified and only meant as pointers):
extendedKeysentry. Its initialvalue comes from the allowlist
["iTerm.app","kitty","WezTerm","ghostty","tmux","windows-terminal","WarpTerminal"]matched against the detected terminal name (which is
TERM_PROGRAMwhen set,otherwise e.g.
vte-based/xterm); for anything else it is "likely false".extendedKeysto true only if the terminalanswers the kitty keyboard query
CSI ? u. There is no modifyOtherKeys probe.extendedKeysis true, the "keys on" sequence is the kitty push(
CSI > 1 u, orCSI > 5 u) followed byCSI > 4 ; 2 m; on exit it isCSI > 4 mplus the kitty pop. So modifyOtherKeys is only ever enabledtogether with the kitty protocol, and only for terminals that either are
allowlisted or speak kitty.
Possible fix:
with its own on/off sequences.
CSI ? u, also send XTQMODKEYS(
CSI ? 4 m). A terminal supporting modifyOtherKeys replies withCSI > 4 ; Pv m; settle the modifyOtherKeys capability on that reply andemit
CSI > 4 ; 2 m(andCSI > 4 mon exit / suspend).CLAUDE_CODE_FORCE_SYNC_OUTPUTfor synchronized output.The input side already decodes the modifyOtherKeys encoding: as noted in the
description, faking an allowlisted terminal makes Ctrl+Enter work.
Workaround until then:
TERM_PROGRAM=WarpTerminal claudeseems to be theallowlisted value with the fewest side effects on a non-Warp terminal (it
still works with
WezTerm, but that disables hyperlinks unlessTERM_PROGRAM_VERSIONis also set, and turns off OSC 133).@bcherny is there anything I can do to help move this forward?