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 (HTTP/2 and HTTP/1.1). `codewhale doctor` can still pass when non-streaming requests work; try `CODEWHALE_FORCE_HTTP1=1` and rerun `codewhale`. What it means
The terminal case of the SSE open sequence: the initial attempt timed out waiting for response headers, policy allowed exactly one HTTP/1.1-only replay, and that replay also timed out. The turn fails with a typed LlmError::NetworkError (deliberately not a bare anyhow string) so the shared retry layer recognizes it as retryable; without the typed error this used to kill the whole turn outright.
Source
Thrown at crates/tui/src/client/stream_entry.rs:210
}
"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),
Ok(Err(err)) => Err(h1_fallback_error(err)),
// Typed, not a bare string: a header stall is a transport
// failure, and `LlmError::NetworkError` is what the shared
// retry layer recognizes as retryable. As an untyped anyhow
// error this killed the whole turn outright.
Err(_elapsed) => Err(anyhow::Error::new(LlmError::NetworkError(format!(
"SSE stream request did not receive response headers after {}s \
(HTTP/2 and HTTP/1.1). `codewhale doctor` can still pass when \
non-streaming requests work; try `CODEWHALE_FORCE_HTTP1=1` and \
rerun `codewhale`.",
open_req.open_timeout.as_secs()
)))),
}
}
/// Format a stable idle-timeout message shared across adapters.
#[must_use]
pub fn idle_timeout_message(
idle: Duration,
bytes_received: usize,
stream_age: Duration,
since_last_chunk: Duration,
) -> String {
format!(View on GitHub (pinned to 0c42157ee5)
Solutions
- Follow the message: try CODEWHALE_FORCE_HTTP1=1 and rerun codewhale to skip the (possibly broken) H2 negotiation path entirely
- Verify streaming reachability from the same host/container with curl -N on the provider SSE endpoint
- Fix the proxy/firewall path: allow long-lived streaming responses for the API host, disable response buffering, or add the host to NO_PROXY
- If doctor passes but every streaming attempt fails on multiple networks, report it as a provider/network-path issue — this error is transport, not auth
Example fix
# before codewhale # no response headers after Ns (HTTP/2 and HTTP/1.1) # after export CODEWHALE_FORCE_HTTP1=1 codewhale # if still failing, test: curl -N https://<provider>/v1/chat/stream
Defensive patterns
Strategy: retry
Try / catch
match result {
Err(err) if matches!(err.downcast_ref::<LlmError>(), Some(LlmError::NetworkError(m)) if m.contains("HTTP/2 and HTTP/1.1")) => {
// both protocols stalled on headers: bounded retry with backoff;
// after N failures, stop and direct the user to network/proxy diagnostics
}
other => other?,
} Prevention
- Verify SSE reachability (curl -N) from the exact host/container before blaming the client
- Whitelist the provider streaming endpoint through firewalls/egress proxies
- Do not treat this as an auth or prompt error — it is typed NetworkError by design
When it happens
Trigger: Both HTTP/2 and the forced HTTP/1.1 replay stall before response headers within open_timeout — symmetric streaming blockage such as a firewall/egress rule dropping long-lived responses, a proxy that buffers both protocols, DNS-level redirection of the streaming host, or a provider streaming-endpoint outage while the non-streaming route still works (doctor green).
Common situations: Locked-down corporate networks that only permit short request/response exchanges; cloud egress proxies; VPN split-tunnel misconfigurations; provider-side streaming degradation.
Related errors
- SSE stream request did not receive response headers after {}
- SSE stream idle timeout after {}s — no data received (bytes_
- SSE stream request failed after HTTP/1.1 fallback: {err}. `c
- Stream read error: {e}
- Stream read error: {e}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/148f7ad3dc4991bf.
Report an issue: GitHub.