
Real-Time Claude Code Progress in Your Terminal or Pipeline: Use --output-format stream-json
Chris Harper
3 min read
Jul 23, 2026 · 04:06 UTC
TL;DR: --output-format stream-json emits one JSON line per event as Claude works — text deltas, tool calls, costs — so you can build live progress bars or CI monitors without parsing screen output.
When you run claude -p "..." the default text output works fine for reading but breaks when you need to parse it programmatically. Three output modes cover different needs:
| Flag | Use case |
|---|---|
--output-format text | Default. Human-readable prose. Hard to parse. |
--output-format json | One JSON blob at the end with result + cost. Great for scripts that need the answer. |
--output-format stream-json | NDJSON (one JSON per line), emitted in real time. Great for progress monitors and streaming UIs. |
What stream-json emits
Each line is a standalone JSON object with a type field. Key event types:
system/init: session metadata, working directory, model in useassistant/text: a token delta —event.delta.textgives the partial stringtool_use: Claude is calling a tool —event.name+event.inputtool_result: the result returned to Clauderesult: final event — containsresult(the answer string),total_cost_usd,session_id,num_turns
Stream text tokens live
claude -p "List the 5 largest files in the current directory" \
--output-format stream-json \
--verbose \
| jq -r 'select(.type == "assistant") | .event.delta.text // empty'
This prints text as it streams — each token appears immediately, same as interactive mode. The --verbose flag ensures the init event with session metadata is included; always pair it with stream-json.
Extract the final result
RESULT=$(claude -p "Summarize the file README.md" \
--output-format stream-json \
| tail -1 \
| jq -r '.result')
echo "$RESULT"
The last line is always the result event.
Track cost per invocation
claude -p "Review src/auth.ts for security issues" \
--output-format stream-json \
| jq -r 'select(.type == "result") | "Cost: $\(.total_cost_usd) | Turns: \(.num_turns)"'
Output: Cost: $0.0031 | Turns: 4
Python: build a live progress display
import subprocess, json
proc = subprocess.Popen(
["claude", "-p", "Audit our API surface for missing rate limits",
"--output-format", "stream-json", "--verbose"],
stdout=subprocess.PIPE, text=True
)
for line in proc.stdout:
event = json.loads(line)
if event.get("type") == "assistant":
print(event["event"]["delta"].get("text", ""), end="", flush=True)
elif event.get("type") == "tool_use":
print(f"\n[tool: {event['event']['name']}]", end="", flush=True)
elif event.get("type") == "result":
print(f"\n\nDone. Cost: ${event['total_cost_usd']:.4f} | Turns: {event['num_turns']}")
Use --output-format json when you only need the final answer (cheaper to process); reach for stream-json when you need real-time visibility, tool-call audit trails, or per-invocation cost logging without a separate dashboard.
Sources: Run Claude Code programmatically — Claude Code Docs · Claude Code stream-json: the output format that changes everything — backgroundclaude.com · Parsing Claude Code stream-json with jq — ytyng.com