fix: chunk multiline PTY writes on macOS to avoid 1024-byte buffer corruption#298993
Conversation
Adds a test that sends multiline commands of varying sizes (10, 20, 30 lines) through TerminalProcess.input() and verifies the data arrives intact at the shell. On macOS, multiline commands exceeding ~1024 bytes corrupt due to PTY canonical-mode input buffer backpressure. Reproduces: microsoft#296955
There was a problem hiding this comment.
Pull request overview
This PR adds a new test file src/vs/platform/terminal/test/node/terminalProcess.test.ts to document and reproduce a macOS PTY bug where multiline commands exceeding approximately 1024 bytes get corrupted (issue #296955). The test spawns a real PTY process via TerminalProcess, sends a heredoc command of varying sizes through TerminalProcess.input(), and verifies the shell received the complete payload by checking the output file contents.
Changes:
- Adds a new test file with 3 test cases (10-, 20-, and 30-line heredoc commands) to reproduce the PTY 1024-byte buffer bug on macOS/Linux.
c21ed81 to
a09dc3f
Compare
…rruption macOS PTY has a ~1024-byte canonical-mode input buffer. When multiline data (containing CR characters) exceeds this threshold, the shell's line editor echoes characters back, creating backpressure that corrupts the write. Write multiline PTY input in 512-byte chunks with 5ms pauses between them to allow the echo buffer to drain. Non-macOS platforms and single-line writes are unaffected. Fixes microsoft#296955
a09dc3f to
0fd94f3
Compare
📬 CODENOTIFYThe following users are being notified based on files changed in this PR: @TyriarMatched files:
|
| } | ||
| this._ptyProcess!.write(data.slice(i, i + 512)); | ||
| if (i + 512 < data.length) { | ||
| await timeout(5); |
There was a problem hiding this comment.
does timeout(0) work here?
There was a problem hiding this comment.
Tested locally — timeout(0) doesn't work. The kernel genuinely needs wall-clock time to drain the PTY echo buffer, not just an event loop yield.
Results with different values:
timeout() |
10 lines (700B) | 20 lines (1.3KB) | 500 lines (32KB) |
|---|---|---|---|
0 |
✅ | ❌ | ❌ |
1 |
✅ | ✅ | ❌ |
5 |
✅ | ✅ | ✅ |
timeout(0)only sub-1KB passes (below the corruption threshold, so chunking isn't even needed)timeout(1)fails at larger payloads (30+ lines)timeout(5)passes up to 500 lines (32KB), 32x the original corruption threshold
Also bumped the "large" test from 30 lines to 500 lines (32KB) to give more confidence at scale.
|
Confirmed locally on macOS in Copilot terminal execution: small multiline heredocs succeed, slightly larger multiline heredocs get corrupted before Python can parse them, while a much longer single-line command still works. Proof from the same session:
So I can independently confirm the symptom is specifically |
Fixes #296955
Problem
macOS PTY has a ~1024-byte canonical-mode input buffer. When multiline commands (containing CR/newline characters) exceed this threshold, the shell's line editor echoes characters back, creating backpressure that corrupts the write — data after ~1024 bytes wraps around and replays earlier buffer content, destroying the remainder of the command and leaving the shell stuck.
This affects any multiline terminal input over ~1KB: heredocs,
gh issue create --body, inline Python/Node scripts, longgit commitmessages, etc.Evidence
The first commit adds a test (without the fix) that failed on macOS CI, reproducing the exact corruption pattern:
The test passed on Linux and Windows — only macOS is affected.
Fix
In
TerminalProcess.input(), when on macOS and the data is multiline and exceeds 512 bytes, write in 512-byte chunks with 5ms pauses between them. This allows the shell's echo buffer to drain between chunks, preventing the backpressure deadlock.Changed files
src/vs/platform/terminal/node/terminalProcess.ts—_writeChunked()method + chunking branch ininput()src/vs/platform/terminal/test/node/terminalProcess.test.ts(new) — integration tests spawning real PTY processes with 10/20/30-line multiline commandsSee also: https://github.com/jcansdale/macos-pty-multiline-bug (standalone reproducers confirming this is a macOS kernel-level issue)