[BUG] keybindings.json accepts unknown action names silently, and the bad binding kills the key instead of falling back to the default

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Jul 27, 2026

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?

keybindings.json validation checks that an action is a string, but never checks that the string is a known action. A typo'd action name is accepted, stored, and resolves at dispatch time to an action that has no handler. The key then does nothing at all, and because the bad binding still wins its context, it also suppresses whatever would otherwise have handled that key.

Worse, an unknown action overrides the default binding for that key in that context, since user blocks are appended after defaults and last-wins per context+key. So a typo does not degrade to the default, it silently kills the key.

There is no user-visible signal. The validator emits nothing for unknown actions, and even the issues it does detect (Unknown context "X") only reach the debug log, which nobody reads unless they already suspect their config.

Three real examples from one hand-written config, all silently dead until I disassembled the binary to check them:

{ "context": "Global",   "bindings": { "ctrl+shift+o": "app:toggleTeammatePreview" } }
{ "context": "Autocomplete", "bindings": { "enter": "autocomplet:accept" } }
{ "context": "Settings", "bindings": { "enter": "settings:close" } }
{ "context": "Doctor",   "bindings": { "f": "doctor:fix" } }
  • app:toggleTeammatePreview does not exist in 2.1.220 (no such string anywhere in the executable). The key is inert.
  • autocomplet:accept is a typo for autocomplete:accept. Because Autocomplete is one of the three contexts that call registerActiveContext, it outranks Chat, so enter resolved to a nonexistent action and the slash-command menu could not be accepted with Enter. Enter did not even fall through to its Chat action.
  • settings:close does not exist. The real default is enter -> select:accept, so this line disabled Enter inside /config.
  • Doctor is not a valid context and doctor:fix is not a valid action. This one at least produces a validation error, but only in the debug log.

What Should Happen?

  1. An unknown action name should be a validation error, listed alongside the existing Unknown context "X" error, with a "did you mean" suggestion. The full action list is already embedded in the binary and already used to render the docs, so the check is a Set lookup.
  2. A binding that fails validation should be dropped, so the key falls back to its default rather than being killed.
  3. Validation issues should be surfaced somewhere a user will see them, for example a one-line notice on startup when keybindings.json has errors, or a section in /doctor. Debug-log-only means these bugs are undiscoverable in practice.

Steps to Reproduce

  1. Write ~/.claude/keybindings.json:
{
  "$schema": "https://www.schemastore.org/claude-code-keybindings.json",
  "bindings": [
    { "context": "Settings", "bindings": { "enter": "settings:close" } }
  ]
}
  1. Start claude, run /config, move to any row, press Enter.

Expected: either the row is accepted (invalid binding dropped, default select:accept applies), or a startup warning tells you settings:close is not a valid action.

Actual: nothing happens. Enter is dead in /config and no message is ever shown.

Is this a regression?

I don't know

Claude Code Version

2.1.220 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

The relevant validation code path emits invalid_action only for typeof action !== "string" && action !== null, plus separate checks for command: naming, voice:pushToTalk on bare letters, duplicate keys, and reserved chords. The known-action list exists in the same bundle (the array beginning "app:interrupt","app:exit",..., 134 entries in 2.1.220) and is what the docs table is generated from, so unknown-action detection is a small addition to an existing pass rather than new machinery.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗