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

  1. Follow the message: try CODEWHALE_FORCE_HTTP1=1 and rerun codewhale to skip the (possibly broken) H2 negotiation path entirely
  2. Verify streaming reachability from the same host/container with curl -N on the provider SSE endpoint
  3. Fix the proxy/firewall path: allow long-lived streaming responses for the API host, disable response buffering, or add the host to NO_PROXY
  4. 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

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


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