[BUG] Vi edit mode ESC timeout too long, requires double-ESC or delay to enter normal mode
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?
tl;dr: please implement John Hawthorn's suggestion and reduce ttimeoutlen to 10ms -- https://www.johnhawthorn.com/2012/09/vi-escape-delays/
When using vi edit mode, pressing ESC to exit insert mode requires either pressing ESC twice or waiting ~500ms before pressing the next key. We suspect this is due to the escape sequence timeout being too long for comfortable vim-style editing.
I note that I use vi line editing in bash on iTerm2 and I do not have this issue. Perhaps the terminal configuration from bash (and ofc from vi editor) tightens this escape sequence timeout?
To repro, enter vi INSERT mode in claude code, then press ESC and note that it takes ~500 ms for -- INSERT -- to disappear.
What Should Happen?
Single ESC immediately exits insert mode; subsequent b moves cursor back one word (vim normal mode behavior).
Error Messages/Logs
Steps to Reproduce
Try pressing ESC and then in ~100ms press b. Note that ESC-b is interpreted as Alt+b (emacs backward-word). Cursor moves back one word, but remains in insert mode. Pressing b again inserts the character "b", rather than again going back as expected. It remains in insert mode.
Claude Model
None
Is this a regression?
No, this never worked
Last Working Version
No response
Claude Code Version
2.1.1
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
Related Issues
- #7391 (ESC key multi-purpose conflicts)
- #10926 (Vi edit mode ESC handling conflicts)
FWIW, here's Claude Code's analysis of how to fix this in React Ink:
Node.js readline has an escapeCodeTimeout option: "The duration readline will wait for a character when reading an ambiguous key sequence... Default: 500 milliseconds"
This is the culprit. Ink uses readline under the hood, inheriting the 500ms default.
The Fix
When creating the readline interface (or equivalent stdin handler), pass a shorter timeout:
import * as readline from 'readline';
const rl = readline.createInterface({
input: process.stdin,
escapeCodeTimeout: 10 // 10ms instead of 500ms
});
readline.emitKeypressEvents(process.stdin, rl);