Keybinding set to null consumes keystroke instead of passing through

Status Fixed / completed
Reported on v2.1.29
Maintainer reply None cached
Activity 4 comments · opened Feb 2, 2026 · closed Aug 25, 2026

Issue Description

When setting a keybinding to null in ~/.claude/keybindings.json to unbind it, the keystroke is still consumed rather than being passed through to the host application.

Environment

  • Claude Code Version: 2.1.29
  • Platform: macOS (Darwin 25.2.0)
  • Context: Running via IntelliJ IDEA plugin with Emacs keybindings

Problem Details

I want to use Ctrl+B for Emacs-style backward-character movement. I've configured keybindings.json to:

  1. Unbind Ctrl+B by setting it to null
  2. Rebind the background task feature to Alt+B
{
    "bindings": [
      {
        "context": "Global",
        "bindings": {
          "ctrl+b": null
        }
      },
      {
        "context": "Task",
        "bindings": {
          "task:background": "alt+b"
        }
      }
    ]
}

Expected Behavior

Setting "ctrl+b": null should cause Claude Code to not handle the Ctrl+B keystroke at all, allowing it to pass through to:

  • The terminal's readline (in CLI mode)
  • The IDE's keymap (in IDEA plugin mode)

Actual Behavior

  • Ctrl+B no longer triggers the "background task" action (good)
  • Ctrl+B does nothing - it is consumed/swallowed (bad)
  • The keystroke never reaches IDEA's Emacs keybindings for cursor movement

Impact

Users who rely on Emacs keybindings (Ctrl+A, Ctrl+B, Ctrl+E, Ctrl+F, etc.) cannot reclaim Ctrl+B for cursor navigation even after unbinding it in Claude Code.

Suggested Fix

When a keybinding is set to null, Claude Code should not register a handler for that keystroke at all, allowing it to propagate to the underlying input system.

View original on GitHub ↗

3 Comments

mtesch-um · 6 months ago

haha... came here to share the bug report claude wrote for _me_ guess we're not the only ones...

----

Unbinding keys in keybindings.json swallows keystrokes instead of passing them through to text input (breaks emacs-style cursor movement)

Description

When a default keybinding is unbound by setting it to null in ~/.claude/keybindings.json, the keystroke is consumed/dropped rather than being passed through to the underlying text input component. This makes it impossible to reclaim standard emacs/readline cursor movement keys (Ctrl+A, Ctrl+E, Ctrl+F, Ctrl+B, etc.) in contexts where Claude Code binds them to app-level actions.

Steps to reproduce

  1. Create ~/.claude/keybindings.json with the following content:

``json
{
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"bindings": [
{
"context": "Confirmation",
"bindings": {
"ctrl+e": null
}
}
]
}
``

  1. Start Claude Code
  2. Trigger a tool call that requires confirmation (e.g. a Bash command that isn't auto-approved)
  3. In the confirmation dialog, type some text in the input field
  4. Press Ctrl+E

Expected behavior

Ctrl+E should move the cursor to the end of the line, as it does in standard readline/emacs-style text input (and as it works in the Chat input context).

Actual behavior

Ctrl+E does nothing. The keystroke is consumed by the keybinding system rather than being passed through to the text input component. Before unbinding, Ctrl+E triggered confirm:toggleExplanation — so the unbinding does remove the action, but the key event is swallowed instead of falling through to the input layer.

Broader impact

This affects any emacs/readline keybinding that conflicts with a default app action in any context:

| Key | Default action that blocks readline | Context |
|---|---|---|
| Ctrl+E | confirm:toggleExplanation | Confirmation |
| Ctrl+E | transcript:toggleShowAll | Transcript |
| Ctrl+B | task:background | Task |

There is no workaround because the keybindings system has no cursor movement actions — it only exposes app-level actions. So you can unbind a key, but you cannot rebind it to cursor movement. Users who rely on emacs-style editing (Ctrl+A/E for line start/end, Ctrl+F/B for char movement, Alt+F/B for word movement) lose these keys in any context where Claude Code has a default binding on them.

Suggested fix

When a keystroke is unbound (set to null) and no other binding matches, the key event should be passed through to the underlying text input component rather than being consumed. This would let standard readline/emacs cursor movement work naturally whenever the app-level binding is removed.

Alternatively (or additionally), expose cursor movement actions (e.g. input:cursorLineEnd, input:cursorLineStart, input:cursorForwardWord, etc.) so users can explicitly bind keys to text editing behavior.

Environment

  • Claude Code: 2.1.34
  • macOS: 26.2 (arm64)
  • Node: v25.5.0
  • Terminal: xterm-256color
tadams42 · 6 months ago

Confirming for Enter key, Chat context, on Linux + WezTerm.

Environment

  • Claude Code: 2.1.39
  • Ubuntu 25.10
  • Terminal: WezTerm (with Kitty protocol enabled)

Description

When Enter key is unbound in ~/.claude/keybindings.json, it becomes completely blocked instead of passing through and inserting new line.

Expected behavior

When Enter key is unbound in ~/.claude/keybindings.json, key presses should insert new lines.

Steps to reproduce

  1. Create ~/.claude/keybindings.json with the following content:

``json
{
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"$docs": "https://code.claude.com/docs/en/keybindings",
"bindings": [
{
"context": "Chat",
"bindings": {
"enter": null
}
}
]
}
``

  1. Start Claude Code
  2. Press Enter key

Pressing Enter key has no effect in Claude Code.

vbichkovsky · 6 months ago

Additional data point: Claude Code's input widget already supports emacs-style keybindings natively. C-f (forward char), C-k (kill line), M-b/M-f (word movement) all work as expected in the Chat input.

C-b is the exception — it's intercepted by the keybinding layer for task:background before reaching the input widget. Setting "ctrl+b": null stops the action but still swallows the keystroke, so it never reaches the input component that would handle it correctly.

This confirms that passthrough is the right fix: the input widget already knows what to do with C-b, it just never receives it.

Environment: Claude Code 2.1.45, Linux, standalone terminal (vterm/Emacs).

— Valentin & Claude

Showing cached comments. Read the full discussion on GitHub ↗