Skip to content

fix(cli): exit cleanly instead of crashing on EPIPE#3038

Merged
schani merged 1 commit into
masterfrom
agent/fix-issue-864
Jul 21, 2026
Merged

fix(cli): exit cleanly instead of crashing on EPIPE#3038
schani merged 1 commit into
masterfrom
agent/fix-issue-864

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

Bug

Piping quicktype's output into a program that closes the pipe early (e.g. quicktype ... | head -n 4) caused quicktype to crash with an unhandled 'error' event instead of exiting quietly, as well-behaved Unix CLIs do (compare cat foo | head -n 1, which exits 0 silently).

Repro:

node dist/index.js --src-lang schema --lang swift --just-types ./test/inputs/schema/vega-lite.schema | head -n 4

Before the fix, stderr contained a raw Node.js stack trace:

node:events:486
      throw er; // Unhandled 'error' event
      ^

Error: write EPIPE
    at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:87:19)
Emitted 'error' event on Socket instance at:
    ...
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'

and the quicktype process exited with code 1.

Root cause

writeOutput() in src/index.ts writes generated code via process.stdout.write(...). When the downstream reader (e.g. head) closes its end of the pipe, subsequent writes fail with EPIPE. Node emits this as an 'error' event on the process.stdout/process.stderr writable streams; since nothing listened for it, Node treated it as an uncaught exception, printing a stack trace and exiting with code 1. The top-level main(...).catch(...) handler only catches promise rejections from main(), not this asynchronous stream error event.

Fix

Register 'error' listeners on process.stdout and process.stderr in the CLI entry point (only when running as the CLI, i.e. inside if (require.main === module)) that exit(0) cleanly when the error is EPIPE, and rethrow any other stream error so real failures are still surfaced.

Test coverage

This is CLI/process-level behavior (an unhandled stream error causing an uncaught-exception crash), not generated-output content, so it isn't expressible through the JSON/JSON-Schema fixture harness which asserts on generated code. Added a unit test, test/unit/cli-epipe.test.ts, that spawns the built CLI (dist/index.js) against test/inputs/schema/vega-lite.schema, destroys its stdout pipe early (simulating | head), and asserts the process exits with code 0 and that stderr contains no EPIPE stack trace / "Unhandled 'error' event" text.

  • Confirmed the test fails on the unfixed code (expected 1 to be 0).
  • Confirmed the test passes with the fix applied.

Verification performed locally

  • npm run build — passes.
  • npx vitest run test/unit — 27 files / 171 tests, all passing (including the new test).
  • Manual re-run of the original repro:
    node dist/index.js --src-lang schema --lang swift --just-types ./test/inputs/schema/vega-lite.schema 2>err.txt | head -n 4
    echo "${PIPESTATUS[@]}"   # 0 0, no EPIPE stack trace in err.txt
    
  • CI will additionally validate the full fixture suite across languages.

Fixes #864

🤖 Generated with Claude Code

Piping quicktype's output into a program that closes the pipe early
(e.g. `... | head -n 4`) left an unhandled 'error' event on
process.stdout/stderr. Node treated the resulting EPIPE as an
uncaught exception, printing a raw stack trace and exiting with code
1 instead of exiting quietly like well-behaved Unix CLIs do.

Add EPIPE error handlers on process.stdout/stderr in the CLI entry
point that exit(0) on EPIPE and rethrow any other stream error.

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
@schani
schani merged commit f69c9c3 into master Jul 21, 2026
28 checks passed
@schani
schani deleted the agent/fix-issue-864 branch July 21, 2026 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Closing quicktype's stdout makes it output an error in some cases

1 participant