[BUG] ctrl+<letter> bindings silently fail on non-Latin keyboard layouts (Cyrillic / Greek / Arabic / ...)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
ctrl+<letter> bindings (ctrl+c, ctrl+e, ctrl+v, …) and super+<letter> bindings silently do nothing when the OS keyboard layout is non-Latin (Ukrainian, Greek, Arabic, Hebrew, Armenian, Georgian, …).
The most visible symptom is ctrl+c not interrupting a turn — users have to switch the OS layout to English just to send the interrupt.
Plain text typing in non-Latin scripts is unaffected, which is why the bug is silent — only modifier combos are broken.
What Should Happen?
ctrl+<letter> and super+<letter> bindings should fire by the physical US-layout key position, regardless of the active OS keyboard layout. Pressing the key physically located at "c" with Ctrl held must interrupt the turn whether the user types Latin c, Cyrillic с, Greek
ψ, Arabic س, etc.
Text typing without ctrl/super must continue to deliver the actual character (Б, ñ, ü, …) to the input field — which already works today.
Error Messages/Logs
No error is printed — the keypress is silently dropped.
The terminal (with current claude-code progressive flags >1u) emits on Ukrainian layout for Ctrl+С:
ESC [ 1089 ; 5 u
ink/parse-keypress.ts decodes codepoint=1089 (Cyrillic с), calls keycodeToName(1089), which returns undefined for codepoints outside printable ASCII 32-126. Key.name = undefined → keybindings/match.ts:getKeyName returns null → no binding matches, including the hardcoded ctrl+c interrupt in keybindings/reservedShortcuts.ts:NON_REBINDABLE.
Steps to Reproduce
- Use a terminal that supports the Kitty keyboard protocol: Ghostty, kitty, iTerm2 with kitty mode enabled, WezTerm, or modern Konsole.
- Switch the OS keyboard layout to Russian (or any non-Latin layout — Ukrainian, Greek, Arabic, Hebrew all reproduce).
- Start a Claude Code session:
claude
- Begin any turn — type a prompt and submit.
- While the model is generating, press Ctrl + С (the key physically located at Latin "c" on a US layout — Cyrillic 'с', Unicode U+0441).
Expected: turn interrupts.
Actual: nothing happens. Have to switch OS layout to English first.
Also reproduces with ctrl+e (clear input), ctrl+v (paste), and every other ctrl+<letter> binding.
A zero-dependency Node reproduction that runs the broken parser and the proposed fixed parser side-by-side:
https://github.com/vetermanve/claude-code-non-latin-ctrl-keys/blob/main/repro/repro-ru-ctrl-c.mjs
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.142 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
Companion repo with full RFC, unified diff, vitest spec, and zero-dep Node reproduction:
https://github.com/vetermanve/claude-code-non-latin-ctrl-keys
- Short-form bug report (same content as this issue):
https://github.com/vetermanve/claude-code-non-latin-ctrl-keys/blob/main/BUG_REPORT.md
- Full RFC:
https://github.com/vetermanve/claude-code-non-latin-ctrl-keys/blob/main/ISSUE.md
- Unified diff:
https://github.com/vetermanve/claude-code-non-latin-ctrl-keys/blob/main/PATCH.diff
Proposed fix (3 files, all backwards-compatible):
- ink/termio/csi.ts — request Kitty progressive flag bit 2 (report alternate keys):
ENABLE_KITTY_KEYBOARD = csi('>5u') // was '>1u'
- ink/parse-keypress.ts — extend CSI_U_RE to capture the optional base-layout-key group:
/^\x1b\[(\d+)(?::(\d+))?(?::(\d+))?(?:;(\d+)(?::\d+)?(?:;[\d:]+)?)?u/
- ink/parse-keypress.ts — in the CSI u branch, when ctrl or super is held and the terminal provided a base-layout-key, route the matcher by that physical (US) key position. Plain typing keeps the raw codepoint so non-Latin text input is untouched.
Spec reference:
https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
Happy to open a PR if that's the preferred routing.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗