Hmbown/CodeWhale · error · LlmError::NetworkError
SSE stream request failed after HTTP/1.1 fallback: {err}. `c
Error message
SSE stream request failed after HTTP/1.1 fallback: {err}. `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
The SSE open path first tries the configured HTTP policy and, when the policy allows it, retries once with HTTP/1.1-only. If that H1 attempt fails at the transport level (a bare transport error, LlmError::NetworkError, or LlmError::Timeout), h1_fallback_error normalizes it into a typed LlmError::NetworkError with this message so the shared retry layer still recognizes it as retryable. Provider-semantic failures (auth, invalid request) downcast inside the guard are preserved un-normalized, never retried as transport problems.
Source
Thrown at crates/tui/src/client/stream_entry.rs:144
|| reqwest_error.is_timeout()
|| reqwest_error.is_request();
}
should_retry_with_h1(policy, &format!("{err:#}"))
}
/// Preserve provider-semantic failures returned by the H1 attempt. Only a
/// transport failure should be normalized into the shared retryable network
/// error; otherwise an auth or invalid-request failure could be retried as if
/// switching protocols had failed.
fn h1_fallback_error(err: anyhow::Error) -> anyhow::Error {
if let Some(llm_error) = err.downcast_ref::<LlmError>()
&& !matches!(llm_error, LlmError::NetworkError(_) | LlmError::Timeout(_))
{
return err;
}
anyhow::Error::new(LlmError::NetworkError(format!(
"SSE stream request failed after HTTP/1.1 fallback: {err}. \
`codewhale doctor` can still pass when non-streaming requests work; \
on Windows or proxy networks, try `CODEWHALE_FORCE_HTTP1=1` and rerun `codewhale`."
)))
}
/// Open an SSE response through the shared transport policy.
///
/// `attempt` builds and sends one wire-specific request on the client
/// selected for the given policy (via [`client_for_policy`]); everything
/// transport-shared lives here:
///
/// - the response-header wait is bounded by `open_req.open_timeout`;
/// - a classified transport failure or header stall on the dual client
/// retries exactly once on the HTTP/1.1 twin;
/// - a failure on an already H1-pinned request never retries;
/// - once response headers have been received the seam never retries —
/// body/stream errors belong to the adapter's decode loop.View on GitHub (pinned to 0c42157ee5)
Solutions
- Set CODEWHALE_FORCE_HTTP1=1 (legacy alias DEEPSEEK_FORCE_HTTP1) and rerun codewhale to pin HTTP/1.1 from the first attempt
- Check HTTP_PROXY/HTTPS_PROXY/NO_PROXY: make sure the provider API host either bypasses the proxy or traverses it correctly for streaming responses
- Run `codewhale doctor` to confirm plain (non-streaming) requests work, isolating the fault to the streaming path
- Verify streaming reachability from the same host with curl -N against the provider SSE endpoint
- Let the shared retry layer retry — the error is typed NetworkError by design; if turns still die, capture and report the full {err} chain
Example fix
# before codewhale # SSE stream request failed after HTTP/1.1 fallback: ... # after export CODEWHALE_FORCE_HTTP1=1 codewhale
Defensive patterns
Strategy: retry
Try / catch
match result {
Err(err) => {
if let Some(LlmError::NetworkError(msg)) = err.downcast_ref::<LlmError>() {
if msg.contains("HTTP/1.1 fallback") {
// transport-class: safe to retry with backoff;
// suggest CODEWHALE_FORCE_HTTP1=1 after repeated failures
}
} else { return Err(err); } // auth/invalid-request: never retry
}
ok => ok?,
} Prevention
- Pin CODEWHALE_FORCE_HTTP1=1 on networks known to break H2 streaming
- Keep provider API hosts out of intercepting proxies (NO_PROXY) where policy allows
- Run codewhale doctor after network changes to separate streaming vs non-streaming faults
When it happens
Trigger: Streaming (SSE) chat requests through proxies/VPNs that break long-lived HTTP/2 streams and also degrade the HTTP/1.1 replay: TLS-intercepting middleboxes, HTTP/2 disabled or buggy on the gateway, connection resets mid-handshake. Non-streaming requests may still succeed, which is exactly why `codewhale doctor` can pass while streaming turns fail.
Common situations: Corporate proxy environments (mitm proxies, response buffering), Windows Schannel/proxy quirks, k8s ingresses without H2 streaming support, VPN or captive networks, remote-development egress filters.
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}
- Stream read error: {e}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/527e6fdcbf64db2c.
Report an issue: GitHub.