claude -p: stdout silently truncated at 65536 bytes when result exceeds pipe buffer (async write dropped on exit)

Open 💬 1 comment Opened Jul 13, 2026 by yasyf

Summary

claude -p --output-format json can emit its entire result as one large single-write JSON blob on stdout. When stdout is a pipe and the payload exceeds the pipe's kernel buffer, the tail of the write is lost if the process exits before Node's queued async write drains: the consumer receives exactly the first 65,536 bytes (the default pipe capacity), cut mid-string, with no error and exit code 0.

Evidence

Two independent captures from a benchmark harness (450 headless runs), both truncated at exactly 65,536 bytes:

  • cut mid-string inside an escaped \n in an embedded tool_result payload;
  • invalid JSON (unterminated), no trailing newline;
  • zero real newlines in the whole file — the transcript is one compact JSON array in what appears to be a single write().

A further 9 runs from the same campaign parsed as valid JSON but were missing ~one billed API call each from the recorded stream (detected because per-model usage reconstructs total_cost_usd exactly while the recorded events do not) — plausibly the same flush-vs-exit race dropping an interior write rather than the tail.

Why it is the pipe path

Writes to a regular file are synchronous in Node; writes to a pipe are asynchronous, and a process.stdout.write() larger than the pipe buffer returns a partial write and queues the remainder. If the process exits before the queue drains, the remainder is silently dropped — a long-documented Node gotcha. Redirecting the child's stdout to a real file (instead of a pipe) at spawn time eliminated the problem for us entirely; nothing else changed.

Impact

Any headless harness that spawns claude -p with piped stdout (the default for essentially every subprocess library) silently loses transcript tails once outputs exceed 64 KiB. The failure is invisible: exit 0, valid-looking prefix, no diagnostic.

Environment

  • Claude Code 2.1.207, macOS (darwin arm64), Node v26.4.0 on the host (the CLI's embedded runtime may differ)
  • Observed under claude -p --output-format json with --settings and MCP config; 2/450 whole-tail truncations + 9/450 suspected interior drops in one campaign

Suggested fixes

  • Flush/drain stdout before exit (await the write queue) in the -p result path, or
  • chunk the result emission, or
  • offer a --output-file <path> for -p (the workaround consumers can't cleanly build themselves is exactly this; codex exec -o is prior art).

Workaround for other consumers

Spawn claude -p with stdout pointed at a real file descriptor (not a pipe) and read the file after exit.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗