Hmbown/CodeWhale · error
SSE stream idle timeout after {}s — no data received (bytes_
Error message
SSE stream idle timeout after {}s — no data received (bytes_received={}, stream_age_ms={}, ms_since_last_chunk={}) What it means
The Anthropic SSE reader enforces an idle timeout: if no bytes arrive within `stream_idle_timeout` (from `[tui] stream_chunk_timeout_secs`, default 900s, clamped 1-3600), the stream terminates with this diagnostics-rich message showing bytes received so far, total stream age, and time since the last chunk. It fires on the `Err(_)` arm of the `tokio::time::timeout` wrapper — the connection is healthy but silent.
Source
Thrown at crates/tui/src/client/anthropic.rs:325
// UTF-8 char (CJK/emoji) split across two network reads is never
// corrupted to U+FFFD. Line boundaries ('\n') are ASCII and can
// never fall inside a multi-byte sequence. (Mirrors chat.rs.)
let mut buffer: Vec<u8> = Vec::new();
let stream_start = std::time::Instant::now();
let mut last_chunk_at = std::time::Instant::now();
let mut bytes_received: usize = 0;
tokio::pin!(byte_stream);
loop {
let chunk = match tokio::time::timeout(stream_idle_timeout, byte_stream.next()).await {
Ok(Some(Ok(chunk))) => chunk,
Ok(Some(Err(e))) => {
yield Err(anyhow::anyhow!("Stream read error: {e}"));
return;
}
Ok(None) => break,
Err(_) => {
yield Err(anyhow::anyhow!(super::stream_entry::idle_timeout_message(
stream_idle_timeout,
bytes_received,
stream_start.elapsed(),
last_chunk_at.elapsed(),
)));
return;
}
};
bytes_received += chunk.len();
last_chunk_at = std::time::Instant::now();
buffer.extend_from_slice(&chunk);
loop {
let line = match super::take_sse_line(&mut buffer) {
Ok(Some(line)) => line,
Ok(None) => break,
Err(err) => {View on GitHub (pinned to 8880682c63)
Solutions
- Raise the idle budget in config: `[tui] stream_chunk_timeout_secs = 1800` (max 3600) if your model legitimately goes silent for long stretches.
- Use the diagnostics fields: `bytes_received=0` → the server never started sending (open/first-token problem, check the model and endpoint); `bytes_received>0` → mid-stream stall (network/proxy issue).
- For buffering proxies, bypass them for the Anthropic domain or force HTTP/1.1 (`CODEWHALE_FORCE_HTTP1=1`).
- Retry the request — the stream is cleanly terminated and the turn can be reissued.
Example fix
# before [tui] stream_chunk_timeout_secs = 60 # too tight for long thinking gaps # after [tui] stream_chunk_timeout_secs = 1800
Defensive patterns
Strategy: retry
Validate before calling
// Config sanity check before long sessions: keep the idle budget generous.
fn stream_idle_budget_ok(configured: Option<u64>) -> bool {
configured.unwrap_or(900) >= 900 // default 900s, clamp range 1..=3600
} Type guard
fn is_idle_timeout_error(e: &anyhow::Error) -> bool {
e.to_string().contains("SSE stream idle timeout")
} Try / catch
match stream.next().await {
Some(Err(e)) if is_idle_timeout_error(&e) => {
let m = e.to_string();
if m.contains("bytes_received=0") {
// server never started: wrong endpoint/model — do not blind-retry, diagnose
diagnose_first_token_path().await;
} else {
// mid-stream stall: safe to retry the turn once with backoff
retry_turn_once().await;
}
}
other => handle(other),
} Prevention
- Set `[tui] stream_chunk_timeout_secs` (default 900, max 3600) above your model's longest silent thinking gap.
- Use the diagnostics fields to route recovery: `bytes_received=0` is an open/first-token problem; `>0` is a mid-stream stall.
- Do not blind-retry `bytes_received=0` cases — check model id and endpoint first.
When it happens
Trigger: Extended server-side thinking gaps with no SSE keepalive/ping events; a stalled upstream between the provider and the client; proxies buffering and withholding chunks; streams paused so long the 900s default (or a user-lowered value) expires. Note `bytes_received=0` means nothing ever arrived after headers; a large value with a small `ms_since_last_chunk` gap means the stream ran long then stalled.
Common situations: Deep-reasoning models that go silent for many minutes; users lowering `stream_chunk_timeout_secs` to make failures snappier and then hitting it during legitimate long generations; corporate proxies that hold SSE responses; very long single completions on slow endpoints.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Stream read error: {e}
- {err}
- Anthropic stream error ({error_type}): {message}
- SSE stream idle timeout after {}s — no data received (bytes_
- SSE stream idle timeout after {}s — no data received (bytes_
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/2b3ef8f5266314c3.
Report an issue: GitHub.