Esc in dialog text fields (plan feedback) silently destroys typed text; keybinding `null` unbind can't defuse it
Description
The plan-review dialog offers a free-text feedback field. A single Esc while typing in it closes the dialog and silently discards all typed text — no confirmation, no recovery. The same applies to text input in permission dialogs.
This lands hardest on vim users: vim mode is not supported in these fields (#14974), so the reflexive Esc to leave INSERT mode — often a fast double-tap — instantly destroys a whole paragraph of plan feedback. This is the dialog-text-field sibling of #87631 (main prompt) and #69181 (agent screen).
Steps to reproduce
- Enable vim mode (
"editorMode": "vim"). - Get Claude to propose a plan; choose the option that opens the free-text feedback field.
- Type several lines of feedback.
- Press
Esc(reflexive for vim users). - The dialog closes; all typed text is gone, unrecoverable.
Expected behavior
Esc in a text-containing dialog field should be non-destructive (leave the field / switch vim mode), or at minimum require confirmation, or push the discarded text somewhere recoverable — the Ctrl+U/Ctrl+Y kill-ring already in the text editor (as noted in #87631) is the model to copy.
What we found trying to work around it via keybindings.json (v2.1.220)
"escape": nullin theConfirmationcontext does not stop the cancel. The debug log confirms the file loads ([keybindings] Loaded 2 user bindings from …/keybindings.json, 0 warnings), and the dispatcher source handles null →{type:"unbound"}— but Escape still cancels the dialog, apparently falling through to a raw handler. Unbinding is therefore useless as a defense.- Remapping Escape to a real action does work —
"escape": "confirm:toggleExplanation"neutralizes it in permission dialogs. - The plan dialog needs a second context defused: with only the
Confirmationremap, Escape still cancels the plan popup via the generic select list'sescape → select:cancel. Protecting one text field requires rebinding Escape in theSelectcontext globally, which breaks Escape-closes-menu everywhere — far too blunt. There is no dedicated context for dialog text fields. - Even with both contexts remapped, a fast double-
Escstill clears the field — that path is hardcoded beneath the keybinding layer and cannot be disabled (same complaint as #87631).
Working (partial) workaround
{
"bindings": [
{ "context": "Confirmation",
"bindings": { "escape": "confirm:toggleExplanation", "ctrl+y": "confirm:no" } },
{ "context": "Select",
"bindings": { "escape": "select:previous", "ctrl+y": "select:cancel" } }
]
}
Costs: Escape no longer closes any select-based menu, ctrl+y shadows kill-ring yank inside dialog fields, and double-Esc remains destructive.
Suggested fixes (any subset helps)
- Make
Escnon-destructive in dialog text fields, or route discarded text through the existing kill ring soCtrl+Ycan restore it. - Make
"key": nullunbinding actually consume the key instead of falling through to hardcoded handlers. - Add a dedicated keybinding context for dialog text inputs so they can be protected without rebinding
Selectglobally. - Support vim mode in these fields (#14974) so
Escmeans mode-switch, matching the main prompt.
Environment
- Claude Code 2.1.220, Linux (Arch, zen kernel), foot terminal
"editorMode": "vim"