Hold input until the child can actually receive it - #35
Merged
Conversation
A shell's line editor calls `tcsetattr` with `TCSAFLUSH` when it starts reading, and that discards whatever is already queued on the pty. Input written a moment too early is therefore not delivered late — it is destroyed. The visible symptom is a command that runs one character short: `cho tervin-block-one` for `echo tervin-block-one`, which is not a useful error but a command that does not exist. This is a product bug, not a test artifact. Tervin writes to panes programmatically as well as on keystrokes, so a restored session or a command Tervin runs on the user's behalf lands in exactly that window. It was found by Linux CI, where a loaded runner widens the window enough to hit reliably, and it had been papered over in the tests with sleeps for as long as the suite has existed. `PtySession` now holds input until the child has taken the terminal out of canonical mode, and releases it in order under the same lock. Reading `ICANON` from the master reports the pty's own line discipline, so this is the child's answer rather than an inference from elapsed time or from output — both of which say only that a prompt was *printed*, which is the earlier and wrong moment. The wait is bounded. Plenty of programs read in canonical mode for their whole life, discard nothing, and must not have their input held: `cat`, a pager. Those hit the bound and the gate opens. Three fixed sleeps come out of the tests as a result, and the PTY suite goes from a bit under 5 seconds to 0.89. Every sleep removed here was approximating this, badly. The remaining prompt waits in the Blocks suite stay: they space successive commands so each forms its own Block, which is a different question. Also fixes two tests that had never tested what they claimed. Every marker in the PTY suite was satisfied by the terminal's echo of the command that was typed, so `a_real_shell_receives_input_and_returns_output` passed on the echo alone and had never once proven output came back from a real shell. Markers are now typed split by an empty quote, so the echo reads `t''ervin-...` and only real output can match. rust 682 to 683. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A real bug, found by Linux CI, that the tests had been papering over with sleeps since the suite was written.
What happens
A shell's line editor calls
tcsetattrwithTCSAFLUSHwhen it starts reading, which discards whatever is already queued on the pty. Input written a moment too early is not delivered late, it is destroyed. The symptom is a command that runs one character short:That is not a useful error. It is a command that does not exist.
Why it is a product bug and not a test artifact
Tervin writes to panes programmatically as well as on keystrokes. A restored session, or a command Tervin runs on the user's behalf, lands squarely in that window. A loaded CI runner widens it enough to hit every time, which is how it surfaced.
The fix
PtySessionholds input until the child has taken the terminal out of canonical mode, then releases it in order under the same lock a normal write takes, so nothing can overtake what was held.Reading
ICANONfrom the master reports the pty's own line discipline, so this is the child's answer. Elapsed time and output both say only that a prompt was printed, which is the earlier and wrong moment. That distinction is the whole bug.The wait is bounded at 1500ms. Plenty of programs read in canonical mode for their entire life, discard nothing, and must not have input held —
cat, a pager. Those hit the bound and the gate opens.Fallout
Three fixed sleeps come out of the tests, and the PTY suite goes from just under 5 seconds to 0.89s. Every sleep removed was approximating this, badly.
It also fixes two tests that had never tested what they claimed. Every marker in the PTY suite was satisfied by the terminal's echo of the command typed, so
a_real_shell_receives_input_and_returns_outputpassed on the echo alone and had never proven output came back from a real shell at all. Markers are now typed split by an empty quote, so only real output can match.rust 682 to 683, clippy and fmt clean.
Verified
macOS 8/8 on the PTY suite and 8/8 on Blocks end-to-end, full workspace green. The
ICANONmechanism is load-independent by construction, which the previous three attempts at this were not.