Hmbown/CodeWhale · error · anyhow::Error
SSE stream request failed: HTTP
Error message
SSE stream request failed: HTTP {status}: {error_text} What it means
The streaming (SSE) Chat Completions request was rejected by the provider with a non-success status before any stream could open. The sanitized error body is re-raised with the HTTP status; additionally, if the provider's error mentions `reasoning_content`, the offending message indices are logged to diagnose thinking-mode replay violations. Raised in `handle_chat_completion_stream` in `crates/tui/src/client/chat.rs` around line 1395.
Solutions
- Read the sanitized `error_text` — for `reasoning_content` complaints, check the logged violation indices and ensure replayed assistant turns carry `reasoning_content`.
- Retry with backoff for 429/5xx statuses.
- Verify API key and provider model access for 401/403/404.
- If thinking-mode replay keeps failing, disable thinking mode for the session or rebuild the offending turn's history.
Defensive patterns
Strategy: retry
Validate before calling
fn stream_open_precheck(api_key: &str, thinking_mode: bool, history: &[Value]) -> Result<(), String> {
if api_key.trim().is_empty() { return Err("missing API key".into()); }
if thinking_mode && history.iter().any(is_assistant_tool_call_without_reasoning) {
return Err("history has assistant tool-call turns missing reasoning_content".into());
}
Ok(())
} Try / catch
match client.create_message_stream(req).await {
Err(e) if e.to_string().contains("SSE stream request failed") => {
let s = extract_status(&e.to_string());
if s == 429 || s >= 500 { retry_with_backoff().await }
else if e.to_string().contains("reasoning_content") { repair_reasoning_replay(); retry_once() }
else { Err(e) }
}
other => other,
} Prevention
- Keep reasoning_content on replayed DeepSeek assistant tool-call turns.
- Add backoff for stream-open 429/5xx; streams are bursty and rate-limit easily.
- Inspect logged thinking-mode violation indices after reasoning_content failures.
- Verify key/model access before opening streams.
When it happens
Trigger: Any non-2xx response when opening the SSE stream: 400 where the provider demands `reasoning_content` on assistant tool-call turns (DeepSeek thinking-mode replay violations), 401/403 auth, 429 rate limit, 5xx provider failure on stream open.
Common situations: DeepSeek thinking mode replaying assistant tool-call turns without `reasoning_content` despite the sanitizer; expired keys; bursts of autocomplete streams hitting rate limits; provider incidents breaking stream establishment.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- ${compactRuntimeError(response.status, body)}
- Responses API error [ ]
- Runtime API event response did not include a readable body
- Stream read error
- Anthropic API error (HTTP )
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/cf5dc9aeec3acefa.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/client/chat.rs:1395
let (response, stream_idle_timeout) = self.open_chat_stream_response(&url, &body).await?;
let status = response.status();
crate::client::record_provider_response(self.api_provider, status.as_u16());
if !status.is_success() {
let raw_error_text = bounded_error_text(response, ERROR_BODY_MAX_BYTES).await;
let error_text = sanitize_http_error_body(
Some(self.api_provider.display_name()),
status.as_u16(),
&raw_error_text,
);
// If DeepSeek rejected for missing reasoning_content despite the
// sanitizer, dump the offending indices so we can diagnose where
// they came from on the next failure.
if error_text.contains("reasoning_content") {
log_thinking_mode_violations(&body);
}
anyhow::bail!("SSE stream request failed: HTTP {status}: {error_text}");
}
let api_provider = self.api_provider;
let base_url = self.base_url.clone();
// Capture transport-shape headers before we consume `response` into
// `bytes_stream()`. They are surfaced in the decode-error log path so
// we can tell HTTP/2 RST_STREAM from chunked-encoding corruption from
// gzip-compressor failure when investigating #103.
let response_headers = format_stream_headers(response.headers());
let byte_stream = response.bytes_stream();
let configured_reasoning_stream_style = self.reasoning_stream_style.clone();
let stream = async_stream::stream! {
use futures_util::StreamExt;
// Emit a synthetic MessageStart
yield Ok(StreamEvent::MessageStart {View on GitHub (pinned to 73e0f67d83)