Terminal: mouse release is never reported to the pty in any tracking mode or encoding; 1002 drag-motion missing too
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
Does this issue occur when all extensions are disabled?: Not directly verifiable — the repro runs over Remote-WSL, which requires that extension to be enabled. No terminal-related extensions are installed on either side (full WSL-side list below; neither side has any terminal/shell/tmux/vim extension). No `terminal.*` settings are customized in user, machine or workspace settings, and the shell rc files contain no mouse-related configuration.
- VS Code Version: 1.109.5 (commit `072586267e68ece9a47aa43f8c108e0dcbf44622`, stable, x64)
- OS Version: Windows 11 `10.0.26100`, Remote-WSL (`ms-vscode-remote.remote-wsl` 0.104.3) → Ubuntu 24.04.4 LTS, kernel `6.6.87.2-microsoft-standard-WSL2`
- Shell: zsh, **no tmux**, `TERM=xterm-256color`
- Terminal renderer: default
- WSL-side extensions: `anthropic.claude-code`, `cweijan.vscode-office`, `github.copilot-chat`, `llvm-vs-code-extensions.vscode-clangd`, `ms-python.{debugpy,python,vscode-pylance}`, `ms-toolsai.{jupyter,jupyter-keymap,jupyter-renderers,vscode-jupyter-cell-tags,vscode-jupyter-slideshow}`, `ms-vscode.cpptools`, `rust-lang.rust-analyzer`
## Summary
The integrated terminal reports mouse **press** events to the pty but **never reports the corresponding release**. This holds in every mouse tracking mode and in **both** SGR (1006) and legacy X10 encodings, so it is not an encoding-path problem.
Independently, motion while a button is held (mode **1002**, *button-event tracking*) is never reported, while motion in mode **1003** (*any-event tracking*) is reported normally.
Consequence: **any TUI application that implements click handling or drag-selection through mouse reporting is unusable in the integrated terminal.** A click never completes — the application sees a press that is never terminated — and a drag never forms.
## Steps to reproduce
1. Open an integrated terminal (this repro: Remote-WSL, zsh, no tmux).
2. Save the script below as `mouseprobe.py`.
3. Run `python3 mouseprobe.py`.
4. In each of the four 6-second phases: **press the left button, drag a short distance, release**.
mouseprobe.py — raw-mode pty listener, decodes both SGR and legacy encodings
```python
#!/usr/bin/env python3
"""Mouse-reporting mode matrix probe: does press / release / motion reach the pty?"""
import sys, os, termios, tty, select, time, re
fd = sys.stdin.fileno()
if not sys.stdin.isatty():
sys.exit("run this in a real terminal")
SGR = re.compile(rb'\x1b\[<(\d+);(\d+);(\d+)([Mm])')
X10 = re.compile(rb'\x1b\[M(...)', re.S)
CASES = [
("1000+1006 normal tracking / SGR", "\x1b[?1000h\x1b[?1006h", "\x1b[?1006l\x1b[?1000l"),
("1000 normal tracking / legacy", "\x1b[?1000h", "\x1b[?1000l"),
("1002+1006 button-event / SGR", "\x1b[?1002h\x1b[?1006h", "\x1b[?1006l\x1b[?1002l"),
("1003+1006 any-event / SGR", "\x1b[?1003h\x1b[?1006h", "\x1b[?1006l\x1b[?1003l"),
]
old = termios.tcgetattr(fd)
results = []
print("\nEach mode runs 6s. On each prompt: PRESS left button, DRAG a little, RELEASE.\n")
try:
for name, on, off in CASES:
print(f"\n>>> {name} -- go, 6s")
sys.stdout.write(on); sys.stdout.flush()
tty.setraw(fd)
end, buf = time.time() + 6, b""
press = rel = motion = raw = 0
while time.time() < end:
if select.select([fd], [], [], 0.2)[0]:
chunk = os.read(fd, 1024); raw += len(chunk); buf += chunk
for m in SGR.finditer(buf):
b, kind = int(m[1]), m[4].decode()
if kind == "m": rel += 1
elif b >= 32: motion += 1
else: press += 1
for m in X10.finditer(buf):
b = m[1][0] - 32
if b == 3: rel += 1
elif b >= 32: motion += 1
else: press += 1
buf = X10.sub(b"", SGR.sub(b"", buf))[-32:]
termios.tcsetattr(fd, termios.TCSADRAIN, old)
sys.stdout.write(off); sys.stdout.flush()
results.append((name, press, rel, motion, raw))
print(f" press {press} / release {rel} / motion {motion} (raw {raw} bytes)")
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
sys.stdout.write("\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l"); sys.stdout.flush()
print("\n================ matrix ================")
print(f"{'mode':<38}{'press':>7}{'release':>9}{'motion':>8}")
for name, p, r, mo, raw in results:
print(f"{name:<38}{p:>7}{r:>9}{mo:>8}")
```
## Actual — VS Code 1.109.5 integrated terminal
```
mode press release motion
1000+1006 normal tracking / SGR 1 0 0
1000 normal tracking / legacy 1 0 0
1002+1006 button-event / SGR 1 0 0
1003+1006 any-event / SGR 1 0 3
```
`release` is **0 in every row**, including the legacy (non-SGR) encoding. `motion` is 0 under 1002 but non-zero under 1003.
Raw bytes for a slow, deliberate press-hold-release in mode 1000+1006 (5 separate clicks, decoded):
```
press M left (77,11)
press M right (77,11)
press M left (77,11)
press M left (77,11)
press M left (77,11)
=== press 5 / release 0 ===
```
Not a single `\x1b[<0;77;11m`.
## Expected
Each mode should report the release. For mode 1000+1006 a single click should produce a pair:
```
\x1b[<0;77;11M
\x1b[<0;77;11m
```
and mode 1002 should additionally report motion with the button bit set (`\x1b[<32;…M`) while the button is held.
## Control — Windows Terminal, same machine, same mouse, same shell, same script
```
press M left (74,24)
release m left (74,24)
press M left (74,24)
release m left (74,24)
press M left (77,22)
release m left (40,23) <- drag: release at a different cell
=== press 3 / release 3 ===
```
Identical everything except the terminal emulator. So this is not the mouse, the driver, the shell, or the script.
## Hypothesis
mousedown works, plain mousemove (1003) works, but **everything that depends on "a button is currently held" is dead** — both the release report and the 1002 drag-motion report. That is the shape you get when the document-level `mouseup` / `mousemove` listeners that should be attached on mousedown never fire (or are never attached).
This is one layer below #328696, which reports a *release* being emitted with `NaN` coordinates and **no matching press** on ctrl+click — that report and this one are plausibly two faces of the same broken press/release bookkeeping.
## Real-world impact
This makes mouse-driven TUIs non-functional in the integrated terminal. Concretely, it is why the Claude Code CLI's fullscreen renderer cannot expand a tool result on click and cannot select-to-copy inside VS Code, while both work in Windows Terminal on the same machine. #281851 (left-click drag selection failing under tmux over a remote) is very likely the same root cause seen through tmux.
## Related
- #281851 — left-click drag selection fails in tmux over remote (likely same root cause)
- #328696 — terminal sends `\x1b[<0;NaN;NaNm` with no matching press on ctrl+click
- #331756 — alt-buffer wheel regression in bundled xterm.js (different symptom, same subsystem)
## Not covered
- Not retested on Insiders.
- Not retested with `--disable-extensions`, since the repro path depends on Remote-WSL. No terminal-related extension is installed (see header).
Contributor guide
Assessment
This issue has not been assessed yet.