{"record":{"id":"18a25241169c05c7","repo":"Hmbown/CodeWhale","slug":"stream-read-error-e-18a252","errorCode":null,"errorMessage":"Stream read error: {e}","messagePattern":"Stream read error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/client/chat.rs","lineNumber":1332,"sourceCode":"                        // hyper / h2 / io error is visible — without this the\n                        // outer \"error decoding response body\" message tells\n                        // us nothing about WHY the stream died.\n                        let mut error_chain = format!(\"{e}\");\n                        let mut current: Option<&(dyn std::error::Error + 'static)> =\n                            std::error::Error::source(&e);\n                        while let Some(source) = current {\n                            error_chain.push_str(&format!(\" -> {source}\"));\n                            current = std::error::Error::source(source);\n                        }\n                        crate::logging::warn(format!(\n                            \"Stream read error: {error_chain} \\\n                             (elapsed: {}ms, bytes_received: {}, ms_since_last_event: {}, headers: {})\",\n                            stream_start.elapsed().as_millis(),\n                            bytes_received,\n                            last_event_at.elapsed().as_millis(),\n                            response_headers,\n                        ));\n                        yield Err(anyhow::anyhow!(\"Stream read error: {e}\"));\n                        break;\n                    }\n                };\n\n                bytes_received = bytes_received.saturating_add(chunk.len());\n                last_event_at = std::time::Instant::now();\n                byte_buf.extend_from_slice(&chunk);\n\n                // Guard against unbounded buffer growth (e.g., malformed stream without newlines)\n                const MAX_SSE_BUF: usize = 10 * 1024 * 1024; // 10 MB\n                if byte_buf.len() > MAX_SSE_BUF {\n                    yield Err(anyhow::anyhow!(\"SSE buffer exceeded {MAX_SSE_BUF} bytes — aborting stream\"));\n                    break;\n                }\n\n                if byte_buf.len() > SSE_BACKPRESSURE_HIGH_WATERMARK {\n                    tokio::time::sleep(Duration::from_millis(SSE_BACKPRESSURE_SLEEP_MS)).await;\n                }","sourceCodeStart":1314,"sourceCodeEnd":1350,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/client/chat.rs#L1314-L1350","documentation":"reqwest/hyper returned an error while reading the response body mid-stream. The client walks the full `Error::source()` chain and logs it (as a `Stream read error` warning) together with transport-shape headers captured before the body was consumed — specifically so HTTP/2 RST_STREAM, chunked-encoding corruption, and gzip-compressor failure can be told apart when investigating issue #103 — then yields this error and stops the stream.","triggerScenarios":"Connection reset mid-stream; HTTP/2 RST_STREAM from an intermediary; chunked-encoding corruption from a proxy; response decompression (gzip) failure; captive-portal or middlebox interference.","commonSituations":"Long streams through proxies/CDNs that cut connections; flaky mobile networks; HTTP/2 interop bugs between hyper and certain servers.","solutions":["Retry with backoff — most mid-body transport errors are transient","Inspect the logged warning: the error chain and headers identify RST_STREAM vs chunked corruption vs gzip failure","If RST_STREAM recurs, force HTTP/1.1 for this connection or bypass the proxy","If gzip failure recurs, disable response compression on the client"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"fn is_stream_read_error(err: &anyhow::Error) -> bool {\n    err.to_string().starts_with(\"Stream read error\")\n}","tryCatchPattern":"match stream.next().await {\n    Some(Err(e)) if is_stream_read_error(&e) => {\n        // Check the warn log's source chain + headers first: RST_STREAM / chunked / gzip\n        backoff_retry_reopen().await;\n    }\n    Some(Err(e)) => return Err(e),\n    other => { /* forward */ }\n}","preventionTips":["Retry mid-body transport errors with backoff before investigating","Use the logged error chain and transport headers to classify RST_STREAM vs chunked corruption vs gzip failure","Force HTTP/1.1 or disable response compression when the failure class repeats"],"tags":["network","reqwest","streaming","http2"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}