stream_error

SSE Event Streaming Only Retryable

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

Causestop_reasonFix
stream_error in SSEnone (stream dies)Retry with backoff
max_tokens hitmax_tokensIncrease max_tokens or resume
Content policynone (error event)Adjust prompt
Client timeoutnone (connection closed)Increase timeout in client
Proxy timeoutnoneSet proxy_read_timeout 600s