⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
packages/loopover-mcp/bin/loopover-mcp.ts:1676-1697's flushStdio() exists specifically to
handle output exceeding the pipe's buffer, per its own comment — but it only drains via the
"drain" event, which never fires if the stream instead errors (e.g. EPIPE, which occurs when
output is piped into a reader that stops reading early, such as | head). A repo-wide grep confirms
zero .on("error", ...) handlers are attached to process.stdout/process.stderr anywhere in
bin/loopover-mcp.ts or lib/*.ts, and none of the ~100+ process.stdout.write(...) call sites
check the write's return value or attach error handling.
A minimal reproduction (a Node script performing the identical unguarded
process.stdout.write() pattern, piped into head -c 100) printed its initial output then crashed
asynchronously with an uncaught Error: write EPIPE — confirming the mechanism. Any large-output CLI
command (e.g. maintain queue, tools, a long --json payload) piped into a reader that closes
early is expected to crash the whole process with an uncaught-exception stack trace, rather than
exit cleanly with a normal exit code.
Requirements
- Attach an
"error" listener to both process.stdout and process.stderr that handles EPIPE
(and any other stream error) by exiting the process cleanly (a normal, documented exit code — not
an uncaught-exception crash), rather than letting the error propagate unhandled.
- Do not change the CLI's normal (non-error) output behavior.
Deliverables
All three Deliverables are required in the same PR.
Test Coverage Requirements
packages/loopover-mcp/** is measured by codecov/patch (99%+ target, branch-counted). The new
test must exercise the actual EPIPE/broken-pipe scenario via a real subprocess spawn (not a mocked
stream), since this is fundamentally a process-level signal-handling behavior.
Expected Outcome
Piping a large-output CLI command into a reader that closes early (e.g. | head) results in a
clean process exit, not an uncaught EPIPE exception crash.
Links & Resources
packages/loopover-mcp/bin/loopover-mcp.ts:1676-1697 (flushStdio, the fix location)
test/unit/support/mcp-cli-harness.ts (existing subprocess-spawn test harness to use)
Context
packages/loopover-mcp/bin/loopover-mcp.ts:1676-1697'sflushStdio()exists specifically tohandle output exceeding the pipe's buffer, per its own comment — but it only drains via the
"drain"event, which never fires if the stream instead errors (e.g.EPIPE, which occurs whenoutput is piped into a reader that stops reading early, such as
| head). A repo-wide grep confirmszero
.on("error", ...)handlers are attached toprocess.stdout/process.stderranywhere inbin/loopover-mcp.tsorlib/*.ts, and none of the ~100+process.stdout.write(...)call sitescheck the write's return value or attach error handling.
A minimal reproduction (a Node script performing the identical unguarded
process.stdout.write()pattern, piped intohead -c 100) printed its initial output then crashedasynchronously with an uncaught
Error: write EPIPE— confirming the mechanism. Any large-output CLIcommand (e.g.
maintain queue,tools, a long--jsonpayload) piped into a reader that closesearly is expected to crash the whole process with an uncaught-exception stack trace, rather than
exit cleanly with a normal exit code.
Requirements
"error"listener to bothprocess.stdoutandprocess.stderrthat handlesEPIPE(and any other stream error) by exiting the process cleanly (a normal, documented exit code — not
an uncaught-exception crash), rather than letting the error propagate unhandled.
Deliverables
process.stdoutandprocess.stderrboth have an"error"listener that handles an EPIPE(or other write error) by exiting cleanly.
test/unit/support/mcp-cli-harness.ts) running a large-output command, pipes its output into aprocess that reads a few bytes and closes, and asserts the CLI subprocess exits cleanly within a
timeout — not via an uncaught-exception stack trace on stderr.
unaffected.
All three Deliverables are required in the same PR.
Test Coverage Requirements
packages/loopover-mcp/**is measured bycodecov/patch(99%+ target, branch-counted). The newtest must exercise the actual EPIPE/broken-pipe scenario via a real subprocess spawn (not a mocked
stream), since this is fundamentally a process-level signal-handling behavior.
Expected Outcome
Piping a large-output CLI command into a reader that closes early (e.g.
| head) results in aclean process exit, not an uncaught EPIPE exception crash.
Links & Resources
packages/loopover-mcp/bin/loopover-mcp.ts:1676-1697(flushStdio, the fix location)test/unit/support/mcp-cli-harness.ts(existing subprocess-spawn test harness to use)