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

A Responses-API SSE stream went quiet for longer than the configured idle timeout (stream_chunk_timeout_secs, default 123s). The streaming loop in crates/tui/src/client/responses.rs:244 wraps every byte_stream.next() poll in tokio::time::timeout; on Elapsed it emits a diagnostic with bytes_received, total stream age, and ms since the last chunk, then aborts the stream.

Source

Thrown at crates/tui/src/client/responses.rs:244

            let mut buffer: Vec<u8> = Vec::new();
            let mut done = false;
            let mut content_block_counter: u32 = 0;
            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);

            while !done {
                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);

                // Process complete SSE lines.
                loop {
                    let line = match super::take_sse_line(&mut buffer) {
                        Ok(Some(line)) => line,
                        Ok(None) => break,

View on GitHub (pinned to 8880682c63)

Solutions

  1. Retry the request - the stream is already aborted and nothing partial is kept
  2. Raise the idle budget: add stream_chunk_timeout_secs = 300 under [network] in the config file
  3. Check intermediaries (nginx, Cloudflare, corporate proxies) for buffering or shorter timeouts on event-stream routes and disable buffering for text/event-stream
  4. If bytes_received=0 on every attempt, reproduce with curl -N against the same endpoint to confirm the provider actually streams
  5. Check the provider status page for degraded streaming capacity

Example fix

# config.toml - before
[network]
# idle budget defaults to 123s

# config.toml - after
[network]
stream_chunk_timeout_secs = 300
Defensive patterns

Strategy: retry

Validate before calling

// Rust caller: budget the stream before starting
let idle = Duration::from_secs(cfg.stream_chunk_timeout_secs()); // >= 123s default
assert!(idle.as_secs() >= expected_time_to_first_token(&model), "idle budget below TTFT estimate");

Try / catch

// Match on the message prefix; only retry when nothing was consumed or the call is idempotent
let mut attempt = 0;
loop {
    match run_streaming_call().await {
        Ok(v) => break v,
        Err(e) if e.to_string().starts_with("SSE stream idle timeout") && attempt < 2 => {
            attempt += 1;
            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;
        }
        Err(e) => return Err(e),
    }
}

Prevention

When it happens

Trigger: Headers succeeded but the body never starts (bytes_received=0), or chunks stop arriving mid-response for > stream_chunk_timeout_secs: server-side generation with no SSE keep-alive comments, a proxy/middlebox that buffers text/event-stream, a half-open TCP connection after a network switch, or an overloaded gateway that accepted the connection then queued indefinitely.

Common situations: Long tool-heavy generations where the provider thinks silently for minutes; VPN or corporate proxy drops; self-hosted vllm/sglang gateways that are slow to first token; default 123s budget left unchanged on a slow endpoint.

Understand the failure class

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/0559dbcbfdc15857. Report an issue: GitHub.