Hmbown/CodeWhale · error

SSE buffer exceeded {MAX_SSE_BUF} bytes — aborting stream

Error message

SSE buffer exceeded {MAX_SSE_BUF} bytes — aborting stream

What it means

Guard against unbounded buffer growth: the raw SSE byte buffer grew past 10 MB without yielding a complete line, so the client aborts the stream. A legitimate SSE response virtually never has 10 MB with no newline; this indicates a malformed stream — data arriving with no line delimiters at all.

Source

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

                             (elapsed: {}ms, bytes_received: {}, ms_since_last_event: {}, headers: {})",
                            stream_start.elapsed().as_millis(),
                            bytes_received,
                            last_event_at.elapsed().as_millis(),
                            response_headers,
                        ));
                        yield Err(anyhow::anyhow!("Stream read error: {e}"));
                        break;
                    }
                };

                bytes_received = bytes_received.saturating_add(chunk.len());
                last_event_at = std::time::Instant::now();
                byte_buf.extend_from_slice(&chunk);

                // Guard against unbounded buffer growth (e.g., malformed stream without newlines)
                const MAX_SSE_BUF: usize = 10 * 1024 * 1024; // 10 MB
                if byte_buf.len() > MAX_SSE_BUF {
                    yield Err(anyhow::anyhow!("SSE buffer exceeded {MAX_SSE_BUF} bytes — aborting stream"));
                    break;
                }

                if byte_buf.len() > SSE_BACKPRESSURE_HIGH_WATERMARK {
                    tokio::time::sleep(Duration::from_millis(SSE_BACKPRESSURE_SLEEP_MS)).await;
                }

                // Process complete SSE lines from the buffer. Strict UTF-8:
                // never `from_utf8_lossy` here — a mid-character TCP split
                // stays in `byte_buf` until `\n`, and a genuinely invalid
                // line fails closed instead of injecting U+FFFD (#5374).
                let mut lines_processed = 0usize;
                loop {
                    let line = match super::take_sse_line(&mut byte_buf) {
                        Ok(Some(line)) => line,
                        Ok(None) => break,
                        Err(err) => {
                            yield Err(anyhow::anyhow!("{err}"));

View on GitHub (pinned to 8880682c63)

Solutions

  1. Verify the endpoint actually returns an SSE stream (correct URL and `stream: true` request shape)
  2. Retry once — an occasional malformed response is transient
  3. Capture the buffered bytes to identify what is actually being sent
  4. If deterministic, the endpoint/URL is wrong or the upstream is broken — report or fix the route
Defensive patterns

Strategy: try-catch

Type guard

fn is_sse_buffer_overflow(err: &anyhow::Error) -> bool {
    err.to_string().starts_with("SSE buffer exceeded")
}

Try / catch

match stream.next().await {
    Some(Err(e)) if is_sse_buffer_overflow(&e) => {
        // 10MB with no newline = not an SSE stream; do not retry the same URL blindly
        return Err(e.context("endpoint is not returning newline-delimited SSE"));
    }
    other => { /* forward */ }
}

Prevention

When it happens

Trigger: Server sends one giant unbroken line (no `\n`); a misbehaving proxy strips newlines; a non-SSE body (binary, JSON blob, HTML error page) is served on the streaming endpoint.

Common situations: Wrong base URL where the endpoint returns a plain JSON body instead of SSE; upstream bug emitting an unterminated payload; intermediary response rewriting.

Related errors


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