[BUG] [Claude Code for VS Code] ESC during IME composition cancels the running task in VS Code extension (root cause located)
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?
Summary
When using a CJK IME (Chinese / Japanese / Korean) in the Claude Code for VS Code extension, pressing ESC to dismiss the IME candidate window also unintentionally cancels the currently running Claude task.
This is a re-report of #41772 (closed as not planned), with runtime evidence + source-level root cause + a 1-line minimal patch, hoping the issue can be reconsidered now that the fix cost is shown to be trivial.
Environment
- Claude Code for VS Code extension: v2.1.169 (Anthropic.claude-code-2.1.169-darwin-arm64)
- VS Code: latest stable
- OS: macOS (Apple Silicon)
- IME: any system / third-party Chinese IME (Sogou, Microsoft Pinyin, macOS built-in pinyin all reproduce)
Reproduction
- Start any Claude task that takes a few seconds (e.g., ask it to read a large file).
- While the task is running, type a few letters with a Chinese IME so that the candidate window appears.
- Notice you typed the wrong pinyin and press ESC to dismiss the candidate window.
- Bug: the running Claude task is interrupted at the same time.
Expected behavior
ESC pressed during IME composition should only be consumed by the IME (close the candidate window / cancel composition) and should not interrupt the running task — consistent with how the Enter key in the same input field already correctly checks isComposing.
Runtime evidence
Captured via Developer: Toggle Keyboard Shortcuts Troubleshooting while reproducing the bug. I typed df to bring up the IME candidate window, then pressed ESC:
17:52:17.678 keydown 'd', keyCode: 229
17:52:17.679 Converted to KeyInComposition (114)
17:52:17.679 Resolving [KeyD]
17:52:17.679 From 1 keybinding entries, no when clauses matched
17:52:17.732 keydown 'f', keyCode: 229
17:52:17.732 Converted to KeyInComposition (114)
17:52:17.732 Resolving [KeyF]
17:52:17.732 From 1 keybinding entries, no when clauses matched
17:52:19.493 keydown Escape, keyCode: 229 ← the relevant one
17:52:19.493 Converted to KeyInComposition (114) ← VS Code knows IME is composing
17:52:19.493 Resolving [Escape]
17:52:19.494 From 89 keybinding entries, no when clauses matched
Three things this log proves:
- VS Code's
KeybindingServicecorrectly identified the ESC asKeyInComposition(keyCode 229→ internal114). - None of the 89 ESC keybindings matched (and as confirmed below, this extension registers no plain
escapekeybinding at all). - The task was still cancelled. Therefore the cancellation must come from a JS listener inside the webview, bypassing VS Code's keybinding system.
Confirming no VS Code keybinding is responsible
package.json of the extension declares only the following ESC-related keybindings (none is plain escape):
{ "command": "claude-vscode.focus", "key": "cmd+escape", ... }
{ "command": "claude-vscode.blur", "key": "cmd+escape", ... }
{ "command": "claude-vscode.editor.open", "key": "cmd+shift+escape", ... }
{ "command": "claude-vscode.terminal.open.keyboard", "key": "cmd+escape", ... }
So this can not be remapped or disabled by users via keybindings.json.
Source-level root cause
File: ~/.vscode/extensions/anthropic.claude-code-2.1.169-darwin-arm64/webview/index.js
After running the bundle through Prettier (--print-width 200), three handlers stand out:
1. The cancel-task listener — missing isComposing check
// ~ line 190685
document.body.addEventListener("keydown", (g) => {
let b = l.activeSession.value;
if (!b || g.defaultPrevented) return;
let _ = document.querySelector(
'.PermissionRequest_permissionRequestContainer[tabindex="0"]'
);
if (
g.key === "Escape" &&
!_ &&
!g.altKey && !g.ctrlKey && !g.metaKey && !g.shiftKey
) (b.interrupt(), f()); // ← interrupt() = cancel task
});
b.interrupt() resolves to:
// ~ line 129830
async interrupt() {
if (!this.claudeChannelId || !this.connection.value) return;
this.connection.value.interruptClaude(this.claudeChannelId);
}
2. The Rewind double-ESC handler — relies on a different guard (and is correct)
// ~ line 186354 (input field's React onKeyDown)
if ($.key === "Escape") {
if (mn || Uo || T || te || he) {
$.preventDefault(); A(!1); Q(!1); W(!1); ye(void 0); Me.current = 0;
return;
}
if (b === "" && f) { // ← only counts the double-tap when input is empty
let Le = Date.now();
if (Le - Me.current <= Mn) {
$.preventDefault(); Me.current = 0; f(); // ← open Rewind picker
return;
}
Me.current = Le;
}
return;
}
This handler does not check isComposing, but it's effectively guarded by the b === "" (empty-input) precondition: while the IME is composing, the input is never empty, so the first ESC pressed during composition never increments the double-tap counter. The user-observable result is correct: ESC #1 dismisses the IME, ESC #2 + ESC #3 open the Rewind picker.
3. The Enter handler in the same function — checks isComposing directly
// ~ line 186385 (right after the Escape handler above)
if ($.key === "Enter" && !$.shiftKey) {
if ($.nativeEvent.isComposing) return; // ← correct guard
...
}
Side-by-side comparison
| Handler | Location | IME guard | Behavior with IME open |
|-------------------|--------------------------------------------|--------------------------------|--------------------------------------------------------------------------------------|
| Cancel task | document.body keydown listener (~190689) | ❌ none | Bug: cancels task on the first ESC, while the user only meant to dismiss the IME |
| Rewind double-ESC | input field React onKeyDown (~186354) | ✅ via b === "" precondition | Correct: 3 presses (1 dismisses IME, 2+3 open Rewind) |
| Enter (submit) | input field React onKeyDown (~186385) | ✅ nativeEvent.isComposing | Correct |
The codebase clearly knows how to handle ESC/Enter during IME composition — both the explicit isComposing check (Enter) and the empty-input precondition (Rewind) prevent the first ESC from misfiring. The cancel-task listener at line 190689 is the only place where neither protection exists, which is why it is the only handler that misbehaves with CJK IMEs.
The Enter handler is literally a few lines below the Escape handler in the same function. The fix should align Escape behavior with Enter.
A user-observable secondary symptom that confirms the model
- With no task running and no IME: pressing ESC twice opens the Rewind to… picker.
- With no task running but the IME candidate window open: it takes three ESC presses to open the Rewind picker.
- With a task running and the IME candidate window open: a single ESC closes the IME and cancels the task.
The asymmetry (1 vs. 3) is exactly what the source predicts: the cancel-task listener on document.body does not gate on isComposing, while the Rewind double-tap requires b === "" and so consumes the first IME-related ESC silently.
Proposed minimal patch (1 line)
Line numbers and variable names below are taken from the published bundle (webview/index.jsinside the v2.1.169.vsix) after running it through Prettier. The original source layout is internal to Anthropic; the patch is offered as a structural suggestion rather than a literal diff.
// line ~190689 (cancel-task listener)
document.body.addEventListener("keydown", (g) => {
let b = l.activeSession.value;
if (!b || g.defaultPrevented) return;
+ if (g.isComposing) return;
let _ = document.querySelector('.PermissionRequest_permissionRequestContainer[tabindex="0"]');
if (g.key === "Escape" && !_ && !g.altKey && !g.ctrlKey && !g.metaKey && !g.shiftKey)
(b.interrupt(), f());
});
This brings the cancel-task listener in line with the Enter handler at line ~186385, which already uses the same guard.
Why this matters
- Chinese / Japanese / Korean users naturally press ESC dozens of times a day to dismiss IME candidate windows.
- Each accidental press silently destroys the in-flight Claude task and any pending tool calls, costing real time and tokens.
- The fix is a single
if (event.isComposing) return;line, with a precedent (the Enter handler) right next to it.
Related issues
- #41772 — same symptom, closed as not planned. This issue adds runtime + source-level evidence and a concrete patch, so I'd kindly ask the team to reconsider.
- #15269 — earlier IME ESC issue (resolved Feb 2026), but for the input submission path, not the task cancellation path covered here.
- #47962 — IME pre-edit buffer cleared by UI redraws (different code path).
Thank you for considering this!
What Should Happen?
ESC pressed during IME composition should only be consumed by the IME (close the candidate window / cancel composition) and should not interrupt the running task — consistent with how the Enter key in the same input field already correctly checks isComposing.
Error Messages/Logs
Steps to Reproduce
- Start any Claude task that takes a few seconds (e.g., ask it to read a large file).
- While the task is running, type a few letters with a Chinese IME so that the candidate window appears.
- Notice you typed the wrong pinyin and press ESC to dismiss the candidate window.
- Bug: the running Claude task is interrupted at the same time.
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
1.1.7
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
VS Code integrated terminal
Additional Information
_No response_
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗