Claude Code Streaming Output: Capturing It in Scripts
When people search "claude code streaming output" they're usually hitting one of two walls: either the terminal output from Claude Code scrolls by too fast to read or log properly, or they're trying to pipe Claude Code into a script and getting garbled text instead of structured data. Both problems have the same root cause — Claude Code's default streaming behavior is built for a human watching a terminal, not for a program consuming output programmatically.
This article covers how streaming actually works in Claude Code, how to capture it cleanly for scripts and CI pipelines, and what to do when you need streaming output inside your own product rather than a terminal.
How Claude Code Streams by Default
In interactive mode, Claude Code prints tokens to stdout as they arrive from the model. This is what gives you the "typing" effect — text appears incrementally instead of waiting for the full response. It's useful for reading along, but it's a raw text stream mixed with terminal control characters (for things like the spinner, tool-call indicators, and diff rendering). If you redirect that output to a file with >, you'll often get a mess of ANSI escape codes instead of clean text.
That's expected — the interactive UI was never meant to be machine-readable.
Capturing Streaming Output Cleanly
For scripting, use Claude Code's non-interactive --print mode combined with an explicit output format:
claude --print --output-format stream-json "Refactor this function for readability" < input.js
stream-json emits newline-delimited JSON events instead of raw terminal text. Each line is a discrete event — message start, content deltas, tool calls, tool results, and message completion. This is the format to reach for whenever you want to:
- Log a Claude Code run to a file without terminal noise
- Feed intermediate output into another process in real time
- Build a wrapper script that reacts to specific event types (e.g., stop as soon as a tool call fails)
A minimal consumer in Node.js might look like this:
import { spawn } from "child_process";
import readline from "readline";
const proc = spawn("claude", [
"--print",
"--output-format", "stream-json",
"Summarize the diff in this repo",
]);
const rl = readline.createInterface({ input: proc.stdout });
rl.on("line", (line) => {
if (!line.trim()) return;
const event = JSON.parse(line);
if (event.type === "content_block_delta") {
process.stdout.write(event.delta.text ?? "");
}
});
This gives you the same incremental output the terminal shows, but as structured events you can filter, log, or forward — without ANSI escape codes getting in the way.
Why stream-json Beats Parsing Raw Text
Trying to regex your way through Claude Code's plain-text streaming output is fragile. Tool-call markers, thinking blocks, and final answers all share the same stdout stream, and formatting can change between versions. stream-json avoids that by giving each event a type field you can branch on:
message_start/message_stop— bracket a full turncontent_block_delta— incremental text or tool-input tokenstool_useevents — when Claude Code invokes a tool (file edit, bash command, etc.)
If your script only cares about the final text, buffer the deltas and discard everything else. If you're building a monitoring dashboard for CI runs, log every event with a timestamp — you'll get a full trace of what the agent did and when.
Backpressure and Timeouts
Long-running Claude Code sessions (large refactors, multi-file edits) can stream for minutes. Two practical issues come up:
- Silent stalls. If no events arrive for a while, don't assume the process hung — some tool calls (running tests, installing dependencies) legitimately take time. Set a generous idle timeout rather than a fixed total timeout.
- Stdout buffering. If you pipe through another program (
claude ... | tee log.txt), make sure that program doesn't buffer full lines before flushing, or you'll lose the "streaming" benefit entirely and just get a big dump at the end.
When You Need Streaming Output in a Product, Not a Terminal
Capturing Claude Code's stream for scripts and CI is one use case. It's a different problem when you're building an actual product feature — a chat UI, a support tool, an internal app — where you need reliable, low-latency streaming responses served over HTTP to a frontend, with authentication, usage tracking, and team access baked in.
Claude Code's CLI streaming isn't designed for that. For product-facing streaming, you want a proper HTTP API with server-sent events, which is what SubToAPI provides on top of your existing Claude access. Instead of spawning a CLI process and parsing stdout, you call a REST endpoint and consume SSE chunks directly:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4",
"stream": true,
"messages": [{"role": "user", "content": "Explain event loop backpressure"}]
}'
Or from JavaScript, reading the stream incrementally in a browser or server context:
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4",
stream: true,
messages: [{ role: "user", content: "Draft a release note" }],
}),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
process.stdout.write(decoder.decode(value));
}
This gets you the same token-by-token responsiveness you're used to from Claude Code's terminal, but with application API keys (sub_live_...), per-request usage metadata, and multi-seat team access instead of a local CLI session. See the streaming docs and messages reference for event formats, or the quickstart to get an API key. Plans start at Solo for solo builders and scale up through Team and Scale tiers — full details on pricing.
Questions
Does Claude Code stream output by default? Yes, in interactive terminal mode Claude Code prints tokens as they arrive. In --print non-interactive mode you get the full response at once unless you also set --output-format stream-json.
How do I get structured streaming output instead of raw text? Use claude --print --output-format stream-json, which emits newline-delimited JSON events you can parse per line instead of dealing with raw terminal text and ANSI codes.
Can I use Claude Code's streaming for a production app? Not directly — it's a CLI tool. For product-facing streaming with authentication and usage tracking, use an HTTP API like SubToAPI that serves SSE streams over standard REST endpoints.