[BUG] --output-format stream-json intermittently hangs — process stops producing output and never exits
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When spawning claude -p --output-format stream-json --verbose as a subprocess with stdout redirected to a file (non-TTY), the CLI intermittently stops producing output and hangs indefinitely. The process never exits and must be force-killed. The {"type":"result"} event is never emitted.
Reproduced on v2.1.85 — 2 out of 12 runs hung when launching 6 concurrent instances with tool-use prompts.
In our reproduced cases, the hang occurred mid-conversation — the last event written was a tool_result ("type":"user"). The CLI delivered the tool result but the next assistant turn never arrived. The process produced zero further output and never exited.
Key observations:
- Stdout redirected to a file (not a TTY), prompt passed via stdin
- Intermittent — same prompt succeeds on some runs and hangs on others
- Concurrency increases reproduction rate — 6 parallel instances reproduced in ~12 runs
- No output on stderr — the process is silently stuck
- The hang can occur mid-conversation (after tool_result, waiting for next assistant turn) or after the final assistant response (result event never written)
Previously reported in #1920, #21099, and #25629 — all auto-closed for inactivity, not fixed.
What Should Happen?
The CLI must always either:
- Complete the conversation, emit
{"type":"result"}, and exit 0, or - Emit an error and exit non-zero
It should never hang with no output and no exit.
Error Messages/Logs
No error on stderr. The process hangs silently.
**Successful run:**
system -> assistant (tool_use) -> rate_limit_event -> user (tool_result) -> assistant (text) -> result
[exits 0]
**Failing run (from our reproduction — run 10):**
system -> system -> system -> assistant (tool_use) -> rate_limit_event -> user (tool_result) -> assistant (tool_use) -> user (tool_result)
[hangs — no more output, no exit, no stderr]
Steps to Reproduce
Save as repro.py and run python repro.py:
"""Launches concurrent claude -p processes to reproduce the hang."""
import subprocess, os, time, tempfile, json, concurrent.futures
CONCURRENCY = 6
TIMEOUT = 120
PROMPTS = [
"Create /tmp/repro_a.txt with 'hello', read it, delete it, confirm deletion.",
"Run `echo test > /tmp/repro_b.txt && cat /tmp/repro_b.txt && rm /tmp/repro_b.txt` and report.",
"Create /tmp/repro_c.py with a fibonacci function, run it, report the result.",
"List files in /tmp, count them, create /tmp/repro_d.txt with that count.",
"Write a bash one-liner to /tmp/repro_e.sh that prints the date, chmod +x, run it.",
"Create /tmp/repro_f.json with {\"key\": \"value\"}, read it, report the key.",
]
def run_once(attempt):
with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=False, encoding="utf-8") as f:
output_file = f.name
stdout_fh = open(output_file, "w", encoding="utf-8")
proc = subprocess.Popen(
["claude", "-p", "--output-format", "stream-json", "--verbose"],
stdin=subprocess.PIPE, stdout=stdout_fh, stderr=subprocess.PIPE, text=True,
)
proc.stdin.write(PROMPTS[attempt % len(PROMPTS)])
proc.stdin.close()
start = time.monotonic()
hung = False
while time.monotonic() - start < TIMEOUT:
try:
proc.wait(timeout=2)
break
except subprocess.TimeoutExpired:
if time.monotonic() - start > 20:
try:
with open(output_file, "r", encoding="utf-8") as f:
content = f.read()
except OSError:
continue
if '"type":"assistant"' in content and '"type":"result"' not in content:
s1 = os.path.getsize(output_file)
time.sleep(5)
if s1 == os.path.getsize(output_file):
hung = True
break
stdout_fh.close()
elapsed = time.monotonic() - start
if proc.poll() is None:
proc.kill()
proc.wait()
with open(output_file, "r", encoding="utf-8") as f:
content = f.read()
has_result = '"type":"result"' in content
has_assistant = '"type":"assistant"' in content
events = []
for line in content.strip().splitlines():
if line.strip():
try: events.append(json.loads(line).get("type", "?"))
except: events.append("ERR")
bug = has_assistant and not has_result and hung
status = "HUNG" if proc.returncode is None or hung else f"exit:{proc.returncode}"
marker = "*** BUG ***" if bug else "ok"
print(f" Run {attempt}: [{status}] {elapsed:.0f}s result={has_result} {marker}")
if bug:
print(f" Last events: {' -> '.join(events[-6:])}")
os.unlink(output_file)
return bug
total = bugs = 0
for batch in range(5):
print(f"--- Batch {batch+1}: {CONCURRENCY} concurrent processes ---")
with concurrent.futures.ThreadPoolExecutor(max_workers=CONCURRENCY) as pool:
futs = [pool.submit(run_once, batch * CONCURRENCY + i) for i in range(CONCURRENCY)]
for f in concurrent.futures.as_completed(futs):
total += 1
if f.result(): bugs += 1
if bugs > 0:
print(f"\nBUG REPRODUCED: {bugs}/{total} runs hung")
break
print(f"Total: {bugs}/{total} hangs")
Our output on v2.1.85 (Arch Linux):
--- Batch 1: 6 concurrent processes ---
Run 1: [exit:0] 12s result=True ok
Run 5: [exit:0] 12s result=True ok
Run 4: [exit:0] 14s result=True ok
Run 0: [exit:0] 15s result=True ok
Run 3: [exit:0] 20s result=True ok
Run 2: [exit:0] 26s result=True ok
--- Batch 2: 6 concurrent processes ---
Run 7: [exit:0] 9s result=True ok
Run 11: [exit:0] 13s result=True ok
Run 8: [exit:0] 19s result=True ok
Run 9: [exit:0] 19s result=True ok
Run 10: [HUNG] 25s result=False *** BUG ***
Last events: assistant -> rate_limit_event -> user -> assistant -> user
Run 6: [HUNG] 32s result=False *** BUG ***
Last events: user -> assistant -> assistant -> user
BUG REPRODUCED: 2/12 runs hung
Concurrency and tool-use prompts are key. Sequential runs with simple prompts may not reproduce.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.85 (Claude Code)
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
Non-interactive/CI environment
Additional Information
Prior issues (all auto-closed, not fixed)
| Issue | Description | Disposition |
|-------|-------------|-------------|
| #1920 | Missing result event in stream-json | Auto-closed (inactivity) |
| #21099 | claude -p never exits when stdout is piped | Auto-closed (inactivity) |
| #25629 | Hangs after result event in stream-json | Auto-closed (inactivity) |
TTY hypothesis
All reports share: stdout is not a TTY. Interactive mode works fine. This points to a race condition or deadlock in the non-TTY exit/cleanup path. Concurrency further increases the rate, suggesting shared resource contention.
Workaround
We monitor the output file for event markers and detect two stall patterns:
- Post-completion stall: last event is
"type":"assistant"with no subsequent tool activity for 30s → force-kill (the final response was sent but result event never came) - Mid-conversation stall: last event is an active marker (
"type":"user"/ tool_result) with no new output for 90s → force-kill (the CLI stopped mid-turn)
Both cases treat the missing result as a retryable error.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗