microsoft / microsoft/vscode

Terminal replay can drop acknowledgements for live output and leave the PTY paused

Open
#335,982 0 comments 0 reactions 1 assignee Claimed by @meganrogge View on GitHub
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

VS Code 1.137.0 (`645f29cc3176500b4b5762ba887cf2a7f0ffdf2c`), macOS 26.2 arm64.

An integrated terminal stopped updating and accepting input after a window reload. The Codex CLI process had finished its response, but its UI thread was blocked in `write()`. A separate shell writing a bell to the same TTY was also blocked.

In the running pty host, this terminal had `_isPtyPaused = true`, `_unacknowledgedCharCount = 10549`, and its node-pty socket was not flowing. Calling `clearUnacknowledgedChars()` for that terminal restored both processes immediately. Other terminals remained readable.

I could not capture the original missing acknowledgements, but found a reproducible race in the replay path:

1. `PersistentTerminalProcess.triggerReplay()` fires the replay event, then calls `clearUnacknowledgedChars()`, allowing live output to resume.
2. `BasePty.handleReplay()` sets `_inReplay = true` while awaiting the replay's `writePromise`.
3. Live data delivered through `BasePty.handleData()` still reaches the renderer during that wait.
4. `LocalPty.acknowledgeDataEvent()` discards acknowledgements while `_inReplay` is true. This includes acknowledgements for live data, leaving the host's count inflated.

To reproduce at class level, instantiate `LocalPty` with a proxy that records acknowledgement calls and connect its data events to `AckDataBufferer`. Hold the replay event's `writePromise` unresolved and deliver 10,001 characters with `handleData()`. Flush the replay and live write callbacks in FIFO order in the same JavaScript task: resolve the replay promise, then acknowledge the live data before the async replay continuation runs.

Using `LocalPty`, `AckDataBufferer`, and the actual xterm write queue loaded by the shipped workbench, the proxy received no acknowledgements. With live data queued until replay completes, it received two 5,000-character acknowledgements and the data retained its order. A separate harness using the release's flow-control methods reproduced a permanently paused host: after another 95,000 characters were delivered and parsed, 10,001 unacknowledged characters remained, above the 5,000-character resume threshold.

The local fix buffers live `handleData()` events while `_inReplay` is true and delivers them in order after clearing that flag. The same harness then leaves one unacknowledged character and the host is not paused.

Relevant release sources: [BasePty](https://github.com/microsoft/vscode/blob/645f29cc3176500b4b5762ba887cf2a7f0ffdf2c/src/vs/workbench/contrib/terminal/common/basePty.ts), [LocalPty](https://github.com/microsoft/vscode/blob/645f29cc3176500b4b5762ba887cf2a7f0ffdf2c/src/vs/workbench/contrib/terminal/electron-browser/localPty.ts), [pty service](https://github.com/microsoft/vscode/blob/645f29cc3176500b4b5762ba887cf2a7f0ffdf2c/src/vs/platform/terminal/node/ptyService.ts).

The interactive incident has not been reproduced in a clean profile with extensions disabled. The class-level reproduction does not involve extensions. This installation also has local patches for cwd detection, Skia Graphite, and the separate `localPty` event-buffer leak; the replay and acknowledgement methods tested above were unchanged before this fix.

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.