Hmbown/CodeWhale · error · LlmError::NetworkError
SSE stream request did not receive response headers after {}
Error message
SSE stream request did not receive response headers after {}s. `codewhale doctor` can still pass when non-streaming requests work; on Windows or proxy networks, try `CODEWHALE_FORCE_HTTP1=1` and rerun `codewhale`. What it means
When the stream policy is Http1Only (pinned via CODEWHALE_FORCE_HTTP1=1), the SSE open request must return response headers within open_timeout. If tokio::time::timeout elapses first, this typed LlmError::NetworkError is returned immediately with the elapsed seconds — there is no protocol fallback because HTTP/1.1 is already the floor. The message notes doctor can still pass because non-streaming requests may respond fine.
Source
Thrown at crates/tui/src/client/stream_entry.rs:186
F: Fn(StreamHttpPolicy) -> Fut,
Fut: Future<Output = Result<reqwest::Response>>,
{
let fallback_reason = match tokio::time::timeout(
open_req.open_timeout,
attempt(open_req.policy),
)
.await
{
Ok(Ok(response)) => return Ok(response),
Ok(Err(err)) => {
if !should_retry_error_with_h1(open_req.policy, &err) {
return Err(err);
}
"transport error before response headers"
}
Err(_elapsed) => {
if open_req.policy == StreamHttpPolicy::Http1Only {
return Err(anyhow::Error::new(LlmError::NetworkError(format!(
"SSE stream request did not receive response headers after {}s. \
`codewhale doctor` can still pass when non-streaming requests work; \
on Windows or proxy networks, try `CODEWHALE_FORCE_HTTP1=1` and rerun `codewhale`.",
open_req.open_timeout.as_secs()
))));
}
"response-header timeout"
}
};
// No response body exists yet, so switching protocols and replaying the
// request is safe. The policy guard above keeps this to exactly one retry.
let h1_req = open_req.clone().with_h1_only();
crate::logging::warn(format!(
"SSE stream {fallback_reason}; retrying once with HTTP/1.1"
));
match tokio::time::timeout(h1_req.open_timeout, attempt(h1_req.policy)).await {
Ok(Ok(response)) => Ok(response),View on GitHub (pinned to 0c42157ee5)
Solutions
- Unset CODEWHALE_FORCE_HTTP1 to let the client try HTTP/2 first (only pin H1 if H2 is confirmed broken on your network)
- Bypass or reconfigure the proxy for the API host: disable response buffering / allow streaming for that destination
- Compare with curl -N --http1.1 from the same environment to confirm the header stall is network-side, not client-side
- If the provider is just slow to first byte, raise the stream open timeout rather than pinning protocols
Example fix
# before export CODEWHALE_FORCE_HTTP1=1 codewhale # did not receive response headers after Ns # after (H2 first; the automatic H1 fallback still covers H2-only breakage) unset CODEWHALE_FORCE_HTTP1 codewhale
Defensive patterns
Strategy: retry
Try / catch
match result {
Err(err) if matches!(err.downcast_ref::<LlmError>(), Some(LlmError::NetworkError(m)) if m.contains("did not receive response headers")) => {
// header stall under Http1Only: retry with backoff a bounded number of times;
// if persistent, unset CODEWHALE_FORCE_HTTP1 or fix the proxy path
}
other => other?,
} Prevention
- Only pin HTTP/1.1 (CODEWHALE_FORCE_HTTP1=1) when H2 streaming is confirmed broken
- Ensure proxies allow long-lived streaming responses without buffering
- Size the stream open timeout to the provider's realistic time-to-first-header
When it happens
Trigger: Running with CODEWHALE_FORCE_HTTP1=1 against a server or proxy that accepts the TCP/TLS connection but stalls before sending response headers: buffering proxies, overloaded upstreams, middleware that drops long-lived streaming responses, or an open_timeout too small for the provider's slow first byte.
Common situations: Forced HTTP/1 plus a corporate proxy that buffers SSE; provider incidents with slow time-to-first-header; high-latency or throttled links; local mock servers that never respond.
Related errors
- SSE stream request did not receive response headers after {}
- SSE stream idle timeout after {}s — no data received (bytes_
- Stream read error: {e}
- Stream read error: {e}
- SSE stream idle timeout after {}s — no data received (bytes_
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/5e9f66f29ab0faef.
Report an issue: GitHub.