Skip to content

fix(mcp): no error listener on stdout/stderr lets a broken pipe crash the CLI with an uncaught EPIPE exception #8691

Description

@JSONbored

⚠️ 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

  • process.stdout and process.stderr both have an "error" listener that handles an EPIPE
    (or other write error) by exiting cleanly.
  • A new test spawns the built CLI (using the existing harness pattern in
    test/unit/support/mcp-cli-harness.ts) running a large-output command, pipes its output into a
    process 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.
  • A regression test confirms normal (non-piped-into-early-closing-reader) command output is
    unaffected.

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions