A try/catch around a write contains a synchronous throw. On a real pipe, process.stdout is a net.Socket and EPIPE is delivered asynchronously as an 'error' event, which no try/catch in the process can contain. With no 'error' listener, that surfaces as an uncaught exception, a stack trace, and a non-zero exit from a command that had already done its work.
This is CLI-wide, not specific to any one command: nothing in bin/hypaware.js installs an 'error' handler on stdout/stderr.
What I could and could not reproduce
Measured on macOS, Node 24.7:
hyp query overview | head -1 (and -2, -5): no stack, clean exit. The block is 3,954 bytes, well inside a 64 KB pipe buffer, so the write completes before the reader's exit can matter.
hyp query sql "select * from ai_gateway_messages limit 3000" | head -1: also clean, because the row-data budget caps the payload before it can exceed the pipe buffer.
- A synthetic dead pipe written from a harness: did not raise an uncaught error either.
So I have no live reproduction on today's surfaces. The gap is structural rather than currently-firing: any command that writes more than a pipe buffer's worth to a reader that has exited would hit it, and nothing in the CLI is set up to survive that.
Fix
Install an 'error' handler on stdout/stderr in bin/hypaware.js that exits quietly (0) on EPIPE and reports anything else. That is the conventional CLI behaviour and would let the wizard's first-look comment claim containment without qualification.
Why this is filed rather than fixed
It changes every command's behaviour on a stream error, which deserves its own PR and its own tests (a real pipe, not a stub whose write throws). Raised in review of #407; the comment and test there were corrected to claim only what they actually pin.
A
try/catcharound awritecontains a synchronous throw. On a real pipe,process.stdoutis anet.Socketand EPIPE is delivered asynchronously as an'error'event, which notry/catchin the process can contain. With no'error'listener, that surfaces as an uncaught exception, a stack trace, and a non-zero exit from a command that had already done its work.This is CLI-wide, not specific to any one command: nothing in
bin/hypaware.jsinstalls an'error'handler on stdout/stderr.What I could and could not reproduce
Measured on macOS, Node 24.7:
hyp query overview | head -1(and-2,-5): no stack, clean exit. The block is 3,954 bytes, well inside a 64 KB pipe buffer, so the write completes before the reader's exit can matter.hyp query sql "select * from ai_gateway_messages limit 3000" | head -1: also clean, because the row-data budget caps the payload before it can exceed the pipe buffer.So I have no live reproduction on today's surfaces. The gap is structural rather than currently-firing: any command that writes more than a pipe buffer's worth to a reader that has exited would hit it, and nothing in the CLI is set up to survive that.
Fix
Install an
'error'handler on stdout/stderr inbin/hypaware.jsthat exits quietly (0) onEPIPEand reports anything else. That is the conventional CLI behaviour and would let the wizard's first-look comment claim containment without qualification.Why this is filed rather than fixed
It changes every command's behaviour on a stream error, which deserves its own PR and its own tests (a real pipe, not a stub whose
writethrows). Raised in review of #407; the comment and test there were corrected to claim only what they actually pin.