stream_error
A stream_error event appears in Claude's Server-Sent Events stream when the connection terminates abnormally before message_stop. The SDK raises this as an exception. Unlike non-streaming errors, you may have received partial content before the failure.
What the raw SSE stream looks like
event: message_start
data: {"type":"message_start","message":{"id":"msg_01...","type":"message"}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}
# ... more deltas ...
event: error
data: {"type":"error","error":{"type":"stream_error","message":"stream terminated unexpectedly"}}
# message_stop NEVER arrives — this is the abnormal termination signal
Fix 1 — Use SDK streaming helpers (recommended)
The SDK's stream() context manager handles the SSE protocol and raises exceptions cleanly:
import anthropic
client = anthropic.Anthropic(
timeout=600.0, # long timeout for streaming
max_retries=3,
)
def stream_with_retry(prompt: str, max_attempts: int = 3):
for attempt in range(max_attempts):
try:
with client.messages.stream(
model="claude-opus-4-5",
max_tokens=4096,
messages=[{"role": "user", "content": prompt}]
) as stream:
full_text = ""
for text in stream.text_stream:
full_text += text
print(text, end="", flush=True)
# Confirm normal completion
final = stream.get_final_message()
assert final.stop_reason == "end_turn"
return full_text
except (anthropic.APIConnectionError, anthropic.InternalServerError) as e:
if attempt == max_attempts - 1:
raise
import time; time.sleep(2 ** attempt)
print(f"\nStream error, retrying ({attempt+1}/{max_attempts})...")
Fix 2 — Handle partial content on failure
When a long-form generation fails mid-stream, you can resume instead of restarting:
import anthropic
client = anthropic.Anthropic(timeout=600.0)
def stream_with_resume(messages: list) -> str:
collected = []
try:
with client.messages.stream(
model="claude-opus-4-5",
max_tokens=8192,
messages=messages,
) as stream:
for text in stream.text_stream:
collected.append(text)
except Exception as e:
partial = "".join(collected)
if partial and len(partial) > 100:
# Resume: append partial as assistant turn and continue
messages = messages + [
{"role": "assistant", "content": partial},
{"role": "user", "content": "Please continue from where you left off."}
]
return partial + stream_with_resume(messages)
raise
return "".join(collected)
Fix 3 — TypeScript streaming with error handling
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ timeout: 600_000 }); // 10 min in ms
async function streamWithRetry(prompt: string): Promise {
for (let attempt = 0; attempt < 3; attempt++) {
try {
const stream = await client.messages.stream({
model: "claude-opus-4-5",
max_tokens: 4096,
messages: [{ role: "user", content: prompt }],
});
let text = "";
for await (const chunk of stream) {
if (
chunk.type === "content_block_delta" &&
chunk.delta.type === "text_delta"
) {
text += chunk.delta.text;
process.stdout.write(chunk.delta.text);
}
}
const finalMsg = await stream.finalMessage();
if (finalMsg.stop_reason !== "end_turn") {
throw new Error(`Unexpected stop_reason: ${finalMsg.stop_reason}`);
}
return text;
} catch (err) {
if (attempt === 2) throw err;
const wait = 1000 * 2 ** attempt;
console.error(`\nStream failed, retrying in ${wait}ms...`, err);
await new Promise((r) => setTimeout(r, wait));
}
}
throw new Error("unreachable");
}
Fix 4 — Reverse proxy timeout (common in production)
If your server sits behind Nginx, AWS ALB, or Cloudflare, streaming responses time out at the proxy level before Claude finishes:
# Nginx — set generous proxy timeouts for streaming endpoints
location /api/stream {
proxy_pass http://backend;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_connect_timeout 30s;
# Critical for SSE — disable buffering so events reach client immediately
proxy_buffering off;
proxy_cache off;
# Required SSE headers
proxy_set_header Connection "";
proxy_http_version 1.1;
# Flush every chunk immediately
proxy_max_temp_file_size 0;
gzip off;
}
# AWS ALB — set idle timeout to 600s in the load balancer settings
# Cloudflare — Enterprise plan supports 600s; otherwise use Workers + streaming
Why the stream stops before finishing
| Cause | stop_reason | Fix |
|---|---|---|
stream_error in SSE | none (stream dies) | Retry with backoff |
| max_tokens hit | max_tokens | Increase max_tokens or resume |
| Content policy | none (error event) | Adjust prompt |
| Client timeout | none (connection closed) | Increase timeout in client |
| Proxy timeout | none | Set proxy_read_timeout 600s |