claude -p: stdout silently truncated at 65536 bytes when result exceeds pipe buffer (async write dropped on exit)
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
\nin 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 jsonwith--settingsand 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
-presult 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 -ois 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.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗