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

  1. Unset CODEWHALE_FORCE_HTTP1 to let the client try HTTP/2 first (only pin H1 if H2 is confirmed broken on your network)
  2. Bypass or reconfigure the proxy for the API host: disable response buffering / allow streaming for that destination
  3. Compare with curl -N --http1.1 from the same environment to confirm the header stall is network-side, not client-side
  4. 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

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


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/5e9f66f29ab0faef. Report an issue: GitHub.