Hmbown/CodeWhale · error · anyhow::Error

runtime SSE frame exceeded {MAX_SSE_FRAME_BYTES} bytes witho

Error message

runtime SSE frame exceeded {MAX_SSE_FRAME_BYTES} bytes without a frame delimiter

What it means

While streaming runtime events over SSE, the app-server buffers chunks until a frame delimiter is found (take_sse_frame). If the buffer grows past MAX_SSE_FRAME_BYTES (16 MiB in this crate) without any frame boundary, it aborts rather than allocate unbounded memory. This is a defensive cap against a misbehaving runtime or a non-SSE endpoint.

Source

Thrown at crates/app-server/src/lib.rs:1282

        writer: &mut W,
        since_seq: u64,
    ) -> Result<(u64, TurnTerminalStatus, Option<String>)> {
        let mut response = self
            .authed(self.client.get(format!(
                "{}/v1/threads/{thread_id}/events?since_seq={since_seq}",
                self.base_url
            )))
            .send()
            .await?
            .error_for_status()?;

        let mut buffer = Vec::new();
        let mut last_seq = since_seq;

        while let Some(chunk) = response.chunk().await? {
            buffer.extend_from_slice(&chunk);
            if buffer.len() > MAX_SSE_FRAME_BYTES {
                bail!(
                    "runtime SSE frame exceeded {MAX_SSE_FRAME_BYTES} bytes without a frame delimiter"
                );
            }
            while let Some(frame_bytes) = take_sse_frame(&mut buffer) {
                let Some((event_name, frame_data)) = parse_sse_frame(&frame_bytes) else {
                    continue;
                };
                let envelope: Value = serde_json::from_str(&frame_data)
                    .with_context(|| format!("invalid SSE json for {event_name}: {frame_data}"))?;
                if let Some(seq) = envelope.get("seq").and_then(Value::as_u64) {
                    last_seq = last_seq.max(seq);
                }
                if envelope.get("turn_id").and_then(Value::as_str) != Some(turn_id) {
                    continue;
                }
                let payload = envelope.get("payload").cloned().unwrap_or(Value::Null);
                match event_name.as_str() {
                    "item.delta" => {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Confirm the stream URL is the runtime's SSE endpoint, not a JSON API that returns one big body.
  2. Reproduce with curl -N against the same URL and check whether events are actually frame-delimited (blank-line separated).
  3. If a legitimate single event exceeds 16 MiB, shrink the payload on the runtime side (paginate, truncate large fields) rather than raising the cap.
  4. Remove any proxy between app-server and runtime that rewrites or buffers the stream.

Example fix

# before: stream pointed at a JSON endpoint returning one huge body
stream_url = format!("{base_url}/threads")

# after: use the runtime's SSE event endpoint
stream_url = format!("{base_url}/events?since_seq={seq}")
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the endpoint speaks SSE before subscribing
let resp = client.get(&stream_url).send().await?;
if !resp.headers().get(CONTENT_TYPE).and_then(|v| v.to_str().ok())
    .map(|v| v.contains("text/event-stream")).unwrap_or(false) {
    anyhow::bail!("not an SSE endpoint; refusing to stream");
}

Try / catch

match server.drain_events(since).await {
    Ok(events) => events,
    Err(e) if e.to_string().contains("SSE frame exceeded") => {
        // do NOT retry unchanged: the endpoint/payload is wrong by construction
        anyhow::bail!("runtime stream misconfigured: {e}")
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Streaming /events (drain_events) from a URL that returns a huge single JSON blob instead of newline-delimited SSE frames; a runtime that emits one enormous event (>16 MiB, e.g. a giant serialized document in one frame); a proxy that strips/buffers SSE delimiters.

Common situations: Pointing the SSE stream_url at a plain JSON REST endpoint, a runtime version that serializes an oversized payload into a single event, or an intermediary (debugging proxy, load balancer) that mangles the event framing.

Related errors


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