Hmbown/CodeWhale · error

Stream read error: {e}

Error message

Stream read error: {e}

What it means

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.

Source

Thrown at crates/tui/src/client/chat.rs:1332

                        // hyper / h2 / io error is visible — without this the
                        // outer "error decoding response body" message tells
                        // us nothing about WHY the stream died.
                        let mut error_chain = format!("{e}");
                        let mut current: Option<&(dyn std::error::Error + 'static)> =
                            std::error::Error::source(&e);
                        while let Some(source) = current {
                            error_chain.push_str(&format!(" -> {source}"));
                            current = std::error::Error::source(source);
                        }
                        crate::logging::warn(format!(
                            "Stream read error: {error_chain} \
                             (elapsed: {}ms, bytes_received: {}, ms_since_last_event: {}, headers: {})",
                            stream_start.elapsed().as_millis(),
                            bytes_received,
                            last_event_at.elapsed().as_millis(),
                            response_headers,
                        ));
                        yield Err(anyhow::anyhow!("Stream read error: {e}"));
                        break;
                    }
                };

                bytes_received = bytes_received.saturating_add(chunk.len());
                last_event_at = std::time::Instant::now();
                byte_buf.extend_from_slice(&chunk);

                // Guard against unbounded buffer growth (e.g., malformed stream without newlines)
                const MAX_SSE_BUF: usize = 10 * 1024 * 1024; // 10 MB
                if byte_buf.len() > MAX_SSE_BUF {
                    yield Err(anyhow::anyhow!("SSE buffer exceeded {MAX_SSE_BUF} bytes — aborting stream"));
                    break;
                }

                if byte_buf.len() > SSE_BACKPRESSURE_HIGH_WATERMARK {
                    tokio::time::sleep(Duration::from_millis(SSE_BACKPRESSURE_SLEEP_MS)).await;
                }

View on GitHub (pinned to 8880682c63)

Solutions

  1. Retry with backoff — most mid-body transport errors are transient
  2. Inspect the logged warning: the error chain and headers identify RST_STREAM vs chunked corruption vs gzip failure
  3. If RST_STREAM recurs, force HTTP/1.1 for this connection or bypass the proxy
  4. If gzip failure recurs, disable response compression on the client
Defensive patterns

Strategy: retry

Type guard

fn is_stream_read_error(err: &anyhow::Error) -> bool {
    err.to_string().starts_with("Stream read error")
}

Try / catch

match stream.next().await {
    Some(Err(e)) if is_stream_read_error(&e) => {
        // Check the warn log's source chain + headers first: RST_STREAM / chunked / gzip
        backoff_retry_reopen().await;
    }
    Some(Err(e)) => return Err(e),
    other => { /* forward */ }
}

Prevention

When it happens

Trigger: 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.

Common situations: Long streams through proxies/CDNs that cut connections; flaky mobile networks; HTTP/2 interop bugs between hyper and certain servers.

Related errors


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