Esc in dialog text fields (plan feedback) silently destroys typed text; keybinding `null` unbind can't defuse it

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Sep 10, 2026

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

  1. Enable vim mode ("editorMode": "vim").
  2. Get Claude to propose a plan; choose the option that opens the free-text feedback field.
  3. Type several lines of feedback.
  4. Press Esc (reflexive for vim users).
  5. 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)

  1. "escape": null in the Confirmation context 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.
  2. Remapping Escape to a real action does work — "escape": "confirm:toggleExplanation" neutralizes it in permission dialogs.
  3. The plan dialog needs a second context defused: with only the Confirmation remap, Escape still cancels the plan popup via the generic select list's escape → select:cancel. Protecting one text field requires rebinding Escape in the Select context globally, which breaks Escape-closes-menu everywhere — far too blunt. There is no dedicated context for dialog text fields.
  4. Even with both contexts remapped, a fast double-Esc still 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 Esc non-destructive in dialog text fields, or route discarded text through the existing kill ring so Ctrl+Y can restore it.
  • Make "key": null unbinding 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 Select globally.
  • Support vim mode in these fields (#14974) so Esc means mode-switch, matching the main prompt.

Environment

  • Claude Code 2.1.220, Linux (Arch, zen kernel), foot terminal
  • "editorMode": "vim"

View original on GitHub ↗