Skip to content

Hold input until the child can actually receive it - #35

Merged
QuintinBotes merged 2 commits into
mainfrom
pty-input-gate
Aug 3, 2026
Merged

Hold input until the child can actually receive it#35
QuintinBotes merged 2 commits into
mainfrom
pty-input-gate

Conversation

@QuintinBotes

Copy link
Copy Markdown
Owner

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 tcsetattr with TCSAFLUSH when 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:

cho tervin-block-one     # for: echo tervin-block-one

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

PtySession holds 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 ICANON from 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_output passed 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 ICANON mechanism is load-independent by construction, which the previous three attempts at this were not.

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>
@QuintinBotes
QuintinBotes enabled auto-merge (squash) August 3, 2026 15:23
@QuintinBotes
QuintinBotes merged commit e821bc8 into main Aug 3, 2026
3 checks passed
@QuintinBotes
QuintinBotes deleted the pty-input-gate branch August 3, 2026 16:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant