{"record":{"id":"148f7ad3dc4991bf","repo":"Hmbown/CodeWhale","slug":"sse-stream-request-did-not-receive-response-header-148f7a","errorCode":null,"errorMessage":"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`.","messagePattern":"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`\\.","errorType":"exception","errorClass":"LlmError::NetworkError","httpStatus":null,"severity":"error","filePath":"crates/tui/src/client/stream_entry.rs","lineNumber":210,"sourceCode":"            }\n            \"response-header timeout\"\n        }\n    };\n\n    // No response body exists yet, so switching protocols and replaying the\n    // request is safe. The policy guard above keeps this to exactly one retry.\n    let h1_req = open_req.clone().with_h1_only();\n    crate::logging::warn(format!(\n        \"SSE stream {fallback_reason}; retrying once with HTTP/1.1\"\n    ));\n    match tokio::time::timeout(h1_req.open_timeout, attempt(h1_req.policy)).await {\n        Ok(Ok(response)) => Ok(response),\n        Ok(Err(err)) => Err(h1_fallback_error(err)),\n        // Typed, not a bare string: a header stall is a transport\n        // failure, and `LlmError::NetworkError` is what the shared\n        // retry layer recognizes as retryable. As an untyped anyhow\n        // error this killed the whole turn outright.\n        Err(_elapsed) => Err(anyhow::Error::new(LlmError::NetworkError(format!(\n            \"SSE stream request did not receive response headers after {}s \\\n             (HTTP/2 and HTTP/1.1). `codewhale doctor` can still pass when \\\n             non-streaming requests work; try `CODEWHALE_FORCE_HTTP1=1` and \\\n             rerun `codewhale`.\",\n            open_req.open_timeout.as_secs()\n        )))),\n    }\n}\n\n/// Format a stable idle-timeout message shared across adapters.\n#[must_use]\npub fn idle_timeout_message(\n    idle: Duration,\n    bytes_received: usize,\n    stream_age: Duration,\n    since_last_chunk: Duration,\n) -> String {\n    format!(","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/client/stream_entry.rs#L192-L228","documentation":"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.","triggerScenarios":"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).","commonSituations":"Locked-down corporate networks that only permit short request/response exchanges; cloud egress proxies; VPN split-tunnel misconfigurations; provider-side streaming degradation.","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"],"exampleFix":"# before\ncodewhale   # no response headers after Ns (HTTP/2 and HTTP/1.1)\n\n# after\nexport CODEWHALE_FORCE_HTTP1=1\ncodewhale   # if still failing, test: curl -N https://<provider>/v1/chat/stream","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match result {\n    Err(err) if matches!(err.downcast_ref::<LlmError>(), Some(LlmError::NetworkError(m)) if m.contains(\"HTTP/2 and HTTP/1.1\")) => {\n        // both protocols stalled on headers: bounded retry with backoff;\n        // after N failures, stop and direct the user to network/proxy diagnostics\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["network","sse","timeout","streaming","http2","http1"],"backgroundTag":"response-header-timeout","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}