Repository navigation
fix(cli): deliver large stdout writes past one pipe buffer - #55893
Parker-Fawcett wants to merge 4 commits into
Conversation
|
The following comment was made by an LLM, it may be inaccurate: |
There was a problem hiding this comment.
This fixes the reported case. With the issue's repro script, the base gives 65536 bytes through a pipe and a slow pipe and drops the end marker. With this PR, both match the 69533-byte file output, | head -c 100 still exits 0, and the new test passes here but fails on the base. Type check is clean.
The public API is unchanged.
Piped output can still be cut off in two cases (inline comments):
- Many small writes: writes of 16 KiB or less still use the original nonblocking stream. I tested 200 writes of 1 KB after importing
@opentui/core, with your wrapper in place, and only 64080 bytes arrived. - Slow readers: after 5 s of
EAGAINthe remaining output is dropped silently and the exit code is still 0.--completions fish | (sleep 7; wc -c)gives 65536.
I'd suggest one approach for every piped write, for example writing everything synchronously when stdout isn't a TTY, or setting the descriptors back to blocking, rather than switching at a size threshold. The test can also be simpler (inline comment).
Fixes #52925
|
AI-assisted native Windows review of The new stream wrapper drops callbacks on writes of at most 16 KiB. It pops the callback from I exercised the exact added stdio block in isolated real subprocesses on Bun 1.4.2 and Node 24.13.0. On each runtime, the unchanged stream control passes all 10 cases; the patched block passes only the two 16,385-byte controls. All eight cases at or below 16,384 bytes lose their callbacks, while every output byte still arrives. Both streams, string/encoding/Buffer overloads and the exact threshold are covered. Please preserve the callback when delegating small writes and add callback regressions alongside the pipe-output test. These checks isolate the added block without importing the complete CLI; they do not validate the original POSIX truncation, full CLI behavior, upstream CI or the repository suite. No production code was changed. reviewed and tested locally. |
2dd921b to
b01c835
Compare
|
Review follow-up: I re-validated against the latest
Scope narrowing in this PR:
Environment matrix in scope is left as-is: macOS source/2.0.29 reliably, Linux 2.0.25 source reproducible; I simply don't have a way to validate the Linux/Windows release binaries, and the change is a no-op for platforms whose stdout stays blocking (each original write succeeds immediately). |
b01c835 to
8bbc7d0
Compare
8bbc7d0 to
2f70401
Compare
|
Review follow-up: I re-validated against the latest
Scope narrowing in this PR:
Environment matrix in scope is left as-is: macOS source/2.0.29 reliably, Linux 2.0.25 source reproducible; I simply don't have a way to validate the Linux/Windows release binaries, and the change is a no-op for platforms whose stdout stays blocking (each original write succeeds immediately). |
ac18b21 to
a00773f
Compare
a00773f to
1cfb8ab
Compare
|
@HootRock Thanks for the careful native-OS review — the small-write callback behavior you flagged was a real regression in Fix in the current commit (
Re reproducibility of #55525 itself: the nonblocking fd transition is tied to the |
|
AI-assisted follow-up at A separate regression is in the new small-console fallback. I exercised the exact added block in real subprocesses: all five small-console methods throw Please capture the original bound console methods before replacing them and add a small-console regression. This is distinct from the existing pipe-size/slow-reader findings. These checks isolate the actual added block with only TypeScript erased. They do not import the complete CLI or verify shipped startup, the POSIX nonblocking transition, repository tests/typecheck or upstream CI. No production code was changed. reviewed and tested locally. |
Route Effect Console through process stdout/stderr and wait for the last write on each stream before exit, so --completions fish is no longer cut at 65536 bytes when piped. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Reworked this in The I removed the bot-added 🤖 Addressed by Claude Code |
There was a problem hiding this comment.
The new approach fixes #55525. With the issue's repro script, the base delivers only 65536 bytes through a pipe and a slow pipe. With this PR, fast, slow (sleep 1 and sleep 7) and file output are all 69533 bytes and end with the end marker. | head -c 100 and | head -1 exit 0, an unknown flag still exits 1, and --help output is the same size as on dev. test/output.test.ts passes here, and its slow-reader test fails on the base. Type check is clean.
All three concerns from my last review are resolved: the 16 KiB size threshold (and the reordering risk) is gone, there is no silent 5 s give-up any more, and the test uses a temp dir with a single piped run.
The public API is unchanged.
No blocking concerns. Only output that goes through Effect's Console (completions, help, errors) is covered now; handlers that call process.stdout.write themselves are left to #52955, as you noted. The --version | cat test adds little beyond the slow-reader test and could be dropped to keep the change minimal.
Related: #52925
Bun makes a piped stdout nonblocking once process.stdout is used, and the TUI preload does so at startup; only then does Bun's console drop output that does not fit the pipe. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Thanks. I'd like to keep the 🤖 Addressed by Claude Code |
|
Thanks. Both are resolved; I checked them at 049e627. With no Your reason for keeping the The latest commit (0dd22f6) only rewrites the comment in |
Issue for this PR
Closes #55525
Type of change
What does this PR do?
--completions fishis about 68 KiB and printed through Effect'sConsole, which defaults to Bun'sconsole.log. The CLI's@opentui/solidpreload leaves stdout nonblocking, and on a nonblocking pipe Bun'sconsole.logdrops whatever doesn't fit once the pipe is full, so piped output stopped at 65536 bytes.process.stdout.writequeues the rest instead, butprocess.exitthrows that queue away unless the last write has called back. So the CLI now provides its ownConsolethat writes throughprocess.stdout/process.stderr, and before itsprocess.exitit waits for the last write on each stream to call back.This replaces my earlier approach (global stream/console patching with chunked
writeSync). That broke short console output, and it could still truncate when the reader was slow.Logging is unchanged: the CLI's loggers write to the log file and
process.stderrdirectly, not throughConsole. Handlers that callprocess.stdout.writethemselves, likeapi get(#52925), aren't changed here; #52955 covers that.How did you verify your code works?
test/output.test.tspipes--completions fishthrough a realshpipe to a reader that sleeps 2 s and compares it with the file output. It fails ondev, and also fails if the wait before exit is removed. A second test checks that--version | catprints and exits 0.| head -c 100exits 0.--help,--version,session --help, bash/zsh completions and an unknown-flag error are byte-identical todev.packages/clisuite: 331 pass, 9 skip, 0 fail.bun typecheckand rootbun run checkpass.Screenshots / recordings
N/A, not a UI change.
Checklist
🤖 Generated with Claude Code
Added by the opencode bot (AI-generated).
Related: #52925