fix: `tool` commands no longer lose a big answer on a pipe
Issue for this PR
Closes #1686
Type of change
- Bug fix
- New feature
- Refactor / code improvement
- Documentation
What does this PR do?
tool run --json printed its answer with console.log, and the CLI then called process.exit(). What the pipe couldn't take yet sat in a buffer that exit threw away, so a reader that fell behind got cut-off JSON and exit 0. tool list --json is 67 KB and could be cut the same way, as could a workspace tool's text output, which has no cap.
tool run, tool list --json, tool context and tool schema now write the whole answer with writeSync, retrying on EAGAIN, before the command returns. The bytes don't change. The docs now say to read stdout while the command runs, because a reader that never drains now blocks the CLI, as it would cat.
A reader that closes early, like | head, still exits 0 with nothing on stderr. A write that really fails, like a full disk, now exits 1, where it used to exit 0 with the answer lost.
How did you verify your code works?
- New tests with a delayed reader. The reader waits for the CLI to exit, up to 20 s, before reading. Without the fix,
tool rundelivered 65,536 bytes andtool list65,464. With it, both arrive whole. - New tests for exit codes. A reader that leaves early exits 0, and the test fails without the
EPIPEbranch. A write that fails exits 1 with the error on stderr. An errored result still exits 1 with the reader gone. - Text mode with a 300 KB output (runner patched temporarily to add it), reader 20 s late: 65,536 bytes before, all 300,009 now.
- Compiled binary, 354,156-byte answer piped to
wc -c. 0.12.9 delivered 229,376 bytes; this branch delivered all of it. - Claude Code's plugin,
/lineageon an 11 MB manifest. 120 tries across answers of 49 KB to 495 KB: 0 failed. On 0.12.9, answers of 354 KB and 495 KB failed 7 of 27 tries. - Real edits on the 354 KB model. All 9 got the plugin's impact note. On 0.12.9, 3 of 10 lost it.
- Gates.
test/altimate/tool-cli/cli.test.ts: 17 pass. Typecheck and Marker Guard pass.
Run here on Linux. A reviewer confirmed the earlier head on macOS. Not run on Windows.
Cost side effects
None. No model calls, warehouse queries or cloud resources.
Screenshots / recordings
Not a UI change.
Checklist
- I have tested my changes locally
- I have not included unrelated changes in this PR
- I checked for cost side effects (model calls, queries or cloud resources that grow with user data) and described them, or wrote "none"
🤖 Generated with Claude Code
https://claude.ai/code/session_01YNQjkaiykbC1WtASw6Yegs
Summary by CodeRabbit
- Bug Fixes
- Large tool command outputs, including JSON output, are now delivered completely to readers of the output stream.
- A reader closing the output stream early no longer causes an otherwise successful command to fail. Genuine output write failures still result in a nonzero exit status.
- Documentation
- Clarified that commands may block if output exceeds pipe capacity before it is read. Documented how early closures and write failures affect exit status.