Gate keybinding contexts on vim mode (NORMAL/INSERT) so scroll and line-editing keys can share a keystroke

Status Open
Reported on v2.1.235
Maintainer reply None cached
Activity 0 comments · opened Aug 19, 2026

Refiling #64992, which was closed as not_planned by the stale bot on 2026-07-07 with no human triage. Adding the root cause, which I traced through the 2.1.235 binary since the original report only had the symptom.

The ask

Let keybindings.json contexts be gated on the vim editor mode, so a binding can apply in NORMAL but not INSERT. Either mode-suffixed contexts (Scroll:normal, Chat:insert) or a mode field on a context block would do.

Why

vim resolves this exact byte collision modally: ctrl+u is half-page-up in NORMAL and delete-to-line-start (i_CTRL-U) in INSERT. Claude Code has both behaviours and a vim mode, but the keybinding layer can't see the mode, so the two can't coexist on one keystroke.

Concretely, with editorMode: vim and this in keybindings.json:

{ "context": "Scroll", "bindings": { "ctrl+u": "scroll:halfPageUp" } }

the Scroll context stays active while the chat input has focus in fullscreen, so ctrl+u is captured for scrolling and the input never sees it.

Reproduction

With the binding above and editorMode: "vim", type any text into the input and press ctrl+u:

| renderer | result |
| --- | --- |
| /tui fullscreen | scrolls half a page, text is left untouched |
| classic | deletes to line start, as expected |

The classic renderer resolves the same keystroke correctly, so the collision is specific to the fullscreen scroll view holding isActive while the chat input has focus. Same for ctrl+d, ctrl+e and ctrl+y.

This follows from what the two renderers own. tui: "fullscreen" uses the alt screen with virtualized scrollback, so the app implements its own scroll view and binds keys to it. tui: "default" stays on the main screen and leaves scrolling to the terminal, so there is no in-app scroll view and nothing shadows the input. Fullscreen took ownership of scrollback from the terminal without the keybinding layer gaining the modal distinction that makes these keys unambiguous in vim.

Root cause

Two separate dispatch paths, only one of which is configurable.

The Scroll bindings register with an isActive gate that resolves to isActive && !<context flag>, where the flag is unset by default. In fullscreen that stays true while the input has focus, which is why the scroll binding wins.

The input's line-editing keys live in a hardcoded map inside the text input component, keyed on the raw ctrl character: a b c d e f h k n p u w y (line start/end, char and word delete, ctrl+u delete-to-line-start, ctrl+k delete-to-line-end, ctrl+y yank). This map is dispatched independently of the keybinding system.

None of those operations appear in the keybinding action registry. The complete chat: namespace is:

chat:cancel, chat:killAgents, chat:cycleMode, chat:cycleProactivity,
chat:attentionUp, chat:attentionDown, chat:modelPicker, chat:fastMode,
chat:thinkingToggle, chat:workflowKeywordToggle, chat:submit, chat:newline,
chat:undo, chat:externalEditor, chat:stash, chat:imagePaste,
chat:clearInput, chat:clearScreen

So a Scroll binding on ctrl+u shadows an operation that has no action name, and therefore cannot be rebound anywhere else. The same applies to ctrl+d, ctrl+e and ctrl+y, which are also both plausible scroll keys and live editing keys.

Note chat:clearInput does not clear the input. It forces a stdout redraw and enters the same press-twice-to-submit-/clear flow as chat:clearScreen; neither calls the input setter. That name is worth correcting separately, and it is why the obvious workaround of rebinding it does nothing.

Why adding actions alone does not fix it

Exposing the editing ops as actions (chat:killToLineStart and friends) would let the kill move to a free key, but it would not restore Cmd+Backspace. macOS terminals emit 0x15 for Cmd+Backspace, which is byte-identical to ctrl+u, so a Scroll binding still swallows it regardless of what else is bound. Only mode gating recovers that key.

Both changes are useful and independent. Mode gating is the one that closes this report; an action registry for the editing ops would additionally help users not running vim mode, who currently have no way to reach those operations at all.

Environment

  • claude code 2.1.235
  • macos 26.5.2, arm64
  • tmux 3.7c, ghostty
  • editorMode: "vim", /tui fullscreen

Related

  • supersedes #64992
  • #53039 and #60785 cover insert-mode remapping and were both auto-closed without triage
  • vimInsertModeRemaps (2.1.208) handles two printable characters only, so it cannot express a ctrl keystroke

View original on GitHub ↗