Hmbown/CodeWhale · error

SSE stream request failed: HTTP {status}: {error_text}

Error message

SSE stream request failed: HTTP {status}: {error_text}

What it means

The streaming (SSE) chat request was rejected with a non-2xx status before any body streamed. As a diagnostic special case, if the sanitized error text contains `reasoning_content`, the client logs `log_thinking_mode_violations` dumping the offending message indices — meaning the sanitizer failed to strip reasoning fields DeepSeek rejects for the selected model.

Source

Thrown at crates/tui/src/client/chat.rs:1230

        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 8880682c63)

Solutions

  1. If the error text mentions `reasoning_content`, check the logged violation indices and strip reasoning blocks for the target model, or switch to the reasoner model
  2. Match thinking-mode settings to a model that supports them
  3. 401/429: fix credentials or back off, per the embedded status
  4. Verify the model ID and stream options against DeepSeek's current API
Defensive patterns

Strategy: retry

Type guard

fn is_retryable_http_status_from_message(msg: &str) -> bool {
    [408, 409, 429, 500, 502, 503, 504]
        .iter()
        .any(|s| msg.contains(&format!("HTTP {s}")))
}

Try / catch

match client.stream(request).await {
    Err(e) if e.to_string().contains("reasoning_content") => {
        // Model/thinking-mode mismatch: strip reasoning blocks or switch model, then retry
        retry_without_reasoning_content()
    }
    Err(e) if is_retryable_http_status_from_message(&e.to_string()) => backoff_retry().await,
    Err(e) => Err(e),
    Ok(stream) => Ok(stream),
}

Prevention

When it happens

Trigger: Sending `reasoning_content`-bearing messages or thinking-mode parameters to a non-reasoning DeepSeek model; 401/429/500-style rejections on the streaming endpoint; invalid `stream` parameters.

Common situations: Switching a session from DeepSeek reasoner to DeepSeek chat while history contains reasoning blocks; enabling thinking mode on a model that does not support it; auth or quota issues surfacing on the stream endpoint.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/cf5dc9aeec3acefa. Report an issue: GitHub.