open-standards/wai/changes/2026-11-10-cli-reader-gone-ends-quietly.md
Transaction Science d992cd68c1 wai, wai-pack and wai-quantum: a reader that stops reading ends the run quietly, as it ends a Unix tool
`wai channel verify ... | head` exited 7 with "failed printing to stdout: Broken pipe". Rust's
runtime ignores SIGPIPE, so a write to a pipe whose reader has gone returns EPIPE, and
print!/println! panic on it; the door's catch_unwind turned the panic into an internal error.
Every command of the binary did the same (an operation's text, its JSON envelope, the help the
door prints itself), and so did wai-pack (exit 101) and wai-quantum (101).

The fix is at the write sites, not in SIGPIPE's disposition at start. Restoring SIG_DFL at start
would end the process on a write to any pipe whose reader has gone, a child's standard input
among them: `wai key` writes a secret to a helper's input and `wai mcp`'s dev room writes to its
child's, and there the command should say what failed rather than vanish. It would also leave
Windows, which has no SIGPIPE, as it was. So each binary declares one module first, with
#[macro_use], whose print!, println!, eprint! and eprintln! write through one function: a
failure of kind BrokenPipe on the process's own standard output or error ends the process as a
Unix tool's ends, by restoring SIGPIPE's default action and raising it (the shell reports 141,
and pipefail sees what it sees for cat), and on Windows with status 141; any other failure
panics with std's own message, as before. The handful of direct writes to stdout (prior-set's
listing, the log follower's lines, wai-pack's listing and fixture bytes, wai-quantum's convert
output) go through the same check. wai-quantum's completions are generated into a buffer first,
since clap_complete panics on a failed write of its own. The unit-test builds keep std's macros,
whose output the harness captures. The log follower of `wai logs` and `wai session list` ended
with success on any write error; a real one is now an internal error.

Tests, each through the binary with a pipe whose reader is closed before it starts (so its
first write finds the reader gone): tests/cli_dx.rs (wai channel verify, wai ops list --json,
wai --help), wai-pack tests/stdout_pipe.rs (fixture list, fixture gen) and wai-quantum-cli
tests/stdout_pipe.rs (methods, methods --json, completions bash) require SIGPIPE on Unix, 141 on
Windows, and nothing on standard error. A real write error still fails, on Linux to /dev/full
(and on Windows, in cli_dx, to a handle open for reading alone): wai exits 7 saying "failed
printing to stdout: No space left on device", wai-pack and wai-quantum fail as before.

On the Framework (Linux): with main's wiring every new reader-gone test fails (wai exit 7 with
the panic's message, wai-pack and wai-quantum 101), and with this change all pass; with the
BrokenPipe arm planted out of each binary's check, each reader-gone test fails again. Clippy on
the wai binary reports the same findings before and after (all in the library, none new);
wai-pack's and wai-quantum-cli's clippy -D warnings, as their workflows run it, pass. On the old
prod Mac Studio (macOS arm64), cli_dx's tests that run there and both crates' reader-gone tests
pass. On cbio-device-0000 (Windows x86_64), cli_dx's two new tests pass: status 141 with nothing
on standard error, and exit 7 for a write to a handle open for reading alone. Stacked on main,
the floor, cli_dx, fleet, studio, doctor, mcp, setup-agent, ops, key, toolchain, target, folds
and channel-lineup tests pass on the Framework, and wai breaking finds no break.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-10 03:20:49 -05:00

1,003 B

wai, wai-pack and wai-quantum end quietly when their reader stops reading

  • A reader gone (wai channel verify ... | head) ends wai without a word, as it ends a Unix tool: on Unix by SIGPIPE, which a shell reports as 141, and on Windows with status 141. Neither is one of the toolchain's exit codes. Until now the next write panicked ("failed printing to stdout: Broken pipe") and wai exited 7, an internal error. Any other failure to write standard output is still the error it was. Only a write to the process's own standard output or error ends it so: a write to a child's input that has gone is still reported by the command that made it.
  • wai-pack and wai-quantum do the same. wai-quantum completions writes its script whole, so a failed write is its error (exit 1) rather than a panic.
  • wai logs and wai session list, following a log, ended with success on any failed write; one other than a reader gone is now an internal error (exit 7).