[BUG] AskUserQuestion preview variant bypasses the keybinding registry — option navigation is unbindable
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
AskUserQuestion renders through two different components, and only one of them goes
through the keybinding system.
Plain variant (no option carries a preview): renders via the shared select
component, which registers its actions with the keybinding hook:
{ "select:next": …, "select:previous": …, "select:accept": …, "select:cancel": … }
// registered with { context: "Select" }
Default Select bindings include j and k, so vim-style navigation works, and
anything a user adds to keybindings.json is honored.
Preview variant (any option carries a preview, giving the side-by-side option
list + preview pane with the press n to add notes footer): registers no
navigation actions. It handles keys with a raw onKeyDown instead:
if (ie.key === "up" || ie.ctrl && ie.key === "p") { … } // previous option
else if (ie.key === "down" || ie.ctrl && ie.key === "n") { … } // next option
else if (ie.key === "return") { … }
else if (ie.key === "n" && !ie.ctrl && !ie.meta) { … } // add notes
else if (ie.key === "escape") { … }
else if (ie.key >= "1" && ie.key <= "9") { … }
Consequences:
j/kdo nothing in this dialog, even though they are defaults in theSelect
context that the sibling variant uses.
- No
keybindings.jsonentry can fix it. The component never queries the registry,
so binding select:next, confirm:next, or anything else has no effect.
nis hardcoded to the notes field, so it cannot be rebound or freed up.
The same component does register context: "Tabs" for tabs:previous/tabs:next,
so custom tab bindings still work there — only option navigation is unreachable. That
asymmetry within a single component is what makes this look like an oversight rather
than a deliberate choice.
The Notes: text input in this variant is a legitimate reason not to let bare letters
navigate while it is focused, but the existing handler already gates on that state
(if (g) { … return }), so registering the select:* actions for the non-input state
would not conflict.
What Should Happen?
The preview variant should register the same select:next / select:previous /select:accept / select:cancel actions under context: "Select" that the plain
variant does, so that both the built-in j/k defaults and user-defined bindings work
in both dialogs. n (add notes) should likewise be a bindable action rather than a
hardcoded letter.
Error Messages/Logs
(none — the keys are silently ignored; nothing is written to the debug log)
Steps to Reproduce
No configuration is required — j/k are already defaults in the Select context.
- Start
claudein an interactive terminal. - Prompt:
Use AskUserQuestion to ask me one question with two options. Do not attach previews. - In the dialog, press
jandk— the cursor moves between options. Correct. - Prompt:
Ask the same question again, but attach a preview string to each option. - The dialog now renders with the side-by-side preview pane. Press
jandk—
nothing happens. Only ↑/↓ and ctrl+p/ctrl+n move the cursor, and j/k
are swallowed. Pressing n opens the notes field.
- Add any binding for this dialog to
~/.claude/keybindings.json—Select,
Confirmation, or any other context — and restart. Step 5 is unchanged.
To confirm the config file itself is loading, add:
{ "context": "Tabs", "bindings": { "h": "tabs:previous", "l": "tabs:next" } }
Ask a two-question AskUserQuestion with previews: h/l switches question tabs in
the same dialog where j/k is dead. The file is loaded; the option list just never
consults it.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
(blank)
Claude Code Version
2.1.223 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other (Ghostty, TERM=xterm-256color, TERM_PROGRAM=ghostty)
Additional Information
Regression field: answering honestly, I could not pin this down. The preview variant
appears to be the newer of the two, so questions that previously rendered through the
plain select path — where j/k worked — now render through the hardcoded handler
when the assistant attaches previews. Whether that counts as a regression or as a new
component that never supported bindings depends on which reading you take. The user
experience is that vim navigation in question dialogs stopped working without any
config change.
~/.claude/keybindings.json in use (unchanged since 2026-07-16, all contexts and
actions still valid in 2.1.223):
{
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"bindings": [
{ "context": "Confirmation", "bindings": { "k": "confirm:previous", "j": "confirm:next" } },
{ "context": "Tabs", "bindings": { "h": "tabs:previous", "l": "tabs:next" } },
{ "context": "DiffDialog", "bindings": { "h": "diff:previousSource", "l": "diff:nextSource" } },
{ "context": "ModelPicker", "bindings": { "h": "modelPicker:decreaseEffort", "l": "modelPicker:increaseEffort" } },
{ "context": "Attachments", "bindings": { "h": "attachments:previous", "l": "attachments:next" } },
{ "context": "Footer", "bindings": { "h": "footer:previous", "j": "footer:down", "k": "footer:up", "l": "footer:next" } }
]
}
Related — this is a recurring pattern where individual dialogs implement rawonKeyDown handlers instead of registering keybinding actions, and each one has to be
reported separately:
- #40258 (closed) —
j/kworks in the/resumedialog but not the/mcpdialog - #437 (closed) — vim-style navigation missing in configuration and message menus
- #72476 (open) — mixed feedback report containing "vim mode enabled, but in some
contexts claude forces me to use arrows up and down"; same symptom, no repro
- #82086 (open) — same preview component, different defect (preview pane truncates
with no way to scroll)
A general fix would be to audit dialogs for raw onKeyDown navigation and route them
through the registry, rather than fixing them one dialog at a time.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗