microsoft / microsoft/vscode

Terminal: a bare modifier keypress clears the selection when the kitty keyboard protocol is active, breaking Cmd+C copy

Open
#336,506 2 comments 0 reactions 1 assignee Claimed by @anthonykim1 View on GitHub
terminal
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

Type: Bug

VS Code Version: 1.137.0 (645f29cc3176500b4b5762ba887cf2a7f0ffdf2c, stable)
OS Version: macOS 26.6.2 (25G83), arm64
Shell: fish 4.9.0 (Homebrew)
Extensions: reproduces in a clean profile — fresh `--user-data-dir`, `--extensions-dir` pointed at an empty directory, no settings changed

## Summary

With `terminal.integrated.enableKittyKeyboardProtocol` at its default (`true`), a **bare modifier keypress clears the terminal selection**. Because Cmd+C delivers the Cmd before the chord completes, Cmd+C can never copy: the selection is already gone when `CopySelection` runs, and it reports "The terminal has no selection to copy".

It only reproduces under a shell that negotiates the protocol. fish 4.x does; zsh does not.

## Minimal reproduction (no clipboard, no keybinding involved)

1. macOS, fish 4.x as the terminal shell. `fish --no-config` is sufficient — no user config needed.
2. Leave `terminal.integrated.enableKittyKeyboardProtocol` at its default `true`.
3. Open a terminal, run `echo hello`.
4. Select `hello` with the mouse.
5. **Tap and release Shift. Press nothing else.**

**Expected:** the selection is unaffected — a modifier alone is not input.
**Actual:** the selection is cleared; the highlight disappears.

Cmd+C is the user-visible consequence:

6. Select `hello` again, press Cmd+C.
**Expected:** `hello` on the clipboard.
**Actual:** notification "The terminal has no selection to copy".

Setting `"terminal.integrated.enableKittyKeyboardProtocol": false` and reloading the window fixes both. Toggling the setting flips the behaviour deterministically.

## Isolation

In one terminal, in one window, switching only the shell:

- `zsh` -> select -> Cmd+C: **copies**
- `exit`, `fish --no-config` -> select -> Cmd+C: **warning**

Ruled out by bisection against a clean profile: user settings, keybindings, extensions (95 of them), `globalStorage` state, and workspace state. The only variable that matters is the shell, and behind it the protocol.

Two things still work, which locates the failure in time:

- **Right-click -> Copy works.** No keyboard involved.
- **`terminal.integrated.copyOnSelection` works.** It copies at mouse-up, before any key is pressed.

So the selection genuinely exists after the drag, and is destroyed by the modifier.

## Cause

Under the kitty keyboard protocol, modifier keys report themselves as key events; in legacy mode a bare modifier sends nothing. That report is being treated as selection-clearing input.

The Cmd+C symptom then follows from `xtermTerminal.copySelection()`, which warns from its `else` branch when `hasSelection()` is false:

```ts
async copySelection(asHtml?: boolean, command?: ITerminalCommand): Promise {
if (this.hasSelection() || (asHtml && command)) {
...
} else {
this._notificationService.warn(localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy'));
}
}
```

Suggested fix: do not treat a modifier-only key report as input for the purpose of clearing the selection.

For reference, fish's startup probe, captured from a pty:

```
\e[?u kitty keyboard protocol query
\e[>0q XTVERSION
\e]11;?\e\\ OSC 11 background colour
\e[?1049h enter alternate screen
\eP+q696e646e\e\\ XTGETTCAP "indn"
\eP+q71756572792d6f732d6e616d65\e\\ XTGETTCAP "query-os-name"
\e[?1049l leave alternate screen
\e[0c DA1
```

## Notes

Likely related to #304765 (Option+F / Option+B word navigation broken in terminal apps when the kitty keyboard protocol is enabled) — same setting, different key path.

Not a keybinding-resolution problem; the command dispatches normally:

```
[KeybindingService]: \ From 11 keybinding entries, matched workbench.action.terminal.copySelection,
when: terminalTextSelectedInFocused || terminalFocus && terminalHasBeenCreated && terminalTextSelected || ...
[KeybindingService]: + Invoking command workbench.action.terminal.copySelection.
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.