{"record":{"id":"1732c13847a5d2d8","repo":"Hmbown/CodeWhale","slug":"sse-stream-idle-timeout-after-idle-secs-s-no-data-received","errorCode":null,"errorMessage":"SSE stream idle timeout after {idle_secs}s — no data received (bytes_received={bytes}, stream_age_ms={age}, ms_since_last_chunk={since})","messagePattern":"SSE stream idle timeout after (.+?)s — no data received \\(bytes_received=(.+?), stream_age_ms=(.+?), ms_since_last_chunk=(.+?)\\)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/client/anthropic.rs","lineNumber":334,"sourceCode":"            let mut bytes_received: usize = 0;\n            let mut ended = false;\n            tokio::pin!(byte_stream);\n\n            loop {\n                if !ended {\n                    match tokio::time::timeout(stream_idle_timeout, byte_stream.next()).await {\n                        Ok(Some(Ok(chunk))) => {\n                            bytes_received += chunk.len();\n                            last_chunk_at = std::time::Instant::now();\n                            buffer.extend_from_slice(&chunk);\n                        }\n                        Ok(Some(Err(e))) => {\n                            yield Err(anyhow::anyhow!(\"Stream read error: {e}\"));\n                            return;\n                        }\n                        Ok(None) => ended = true,\n                        Err(_) => {\n                            yield Err(anyhow::anyhow!(super::stream_entry::idle_timeout_message(\n                                stream_idle_timeout,\n                                bytes_received,\n                                stream_start.elapsed(),\n                                last_chunk_at.elapsed(),\n                            )));\n                            return;\n                        }\n                    }\n                }\n\n                loop {\n                    let line = match next_sse_line(&mut buffer, ended) {\n                        Ok(Some(line)) => line,\n                        Ok(None) => break,\n                        Err(err) => {\n                            yield Err(anyhow::anyhow!(\"{err}\"));\n                            return;\n                        }","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/client/anthropic.rs#L316-L352","documentation":"This error is raised in the Anthropic streaming client when no bytes arrive from the Messages API SSE stream for longer than the configured idle timeout (stream_idle_timeout). The tokio::time::timeout wrapping byte_stream.next() fires with Err(_), and the stream yields this diagnostic message with bytes_received, stream age, and time since the last chunk to aid debugging. It aborts the stream immediately rather than letting a stalled connection hang the turn.","triggerScenarios":"Any Anthropic streaming request where the HTTP connection stays open but delivers zero bytes for stream_idle_timeout: provider outage mid-stream, network path stalls (VPN, proxy keepalive drop), a hung model, or a proxy that accepts the request but never relays the response body.","commonSituations":"Corporate proxies buffering SSE indefinitely; long 'thinking' pauses exceeding the configured timeout; Anthropic API partial outages; mobile/unstable networks dropping the connection silently without TCP FIN.","solutions":["Increase stream_idle_timeout in the client configuration if long pauses between chunks are expected for your model.","Check Anthropic status / retry the request — a mid-stream stall is usually transient.","Verify no proxy or VPN is buffering or dropping SSE connections; try a direct connection.","Enable network-level keepalives or use a provider/proxy that sends SSE comments as heartbeats."],"exampleFix":"// before: default timeout too short for extended thinking\nlet client = AnthropicClient::new().with_stream_idle_timeout(Duration::from_secs(30));\n// after\nlet client = AnthropicClient::new().with_stream_idle_timeout(Duration::from_secs(300));","handlingStrategy":"retry","validationCode":"// before the call: ensure timeout is sane\nassert!(idle_timeout >= Duration::from_secs(30), \"idle timeout too small for LLM streams\");","typeGuard":null,"tryCatchPattern":"// stream errors are anyhow::Error items on the stream\nwhile let Some(item) = stream.next().await {\n    match item {\n        Ok(ev) => handle(ev),\n        Err(e) if e.to_string().contains(\"idle timeout\") => {\n            warn!(\"stream stalled: {e}\");\n            retry_with_backoff();\n        }\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["Set stream_idle_timeout generously for reasoning models with long quiet periods","Use proxies that forward SSE immediately (disable buffering)","Monitor provider status pages and add circuit-breaking","Enable TCP/application-level keepalives"],"tags":["network","sse","timeout","streaming"],"backgroundTag":"request-timeout","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}