openai/codex · error · io::Error

InvalidData

InvalidData

Error message

oversized MCP SSE event was already rejected

What it means

SseEventSizeLimit::observe() is sticky: once an SSE event breached the per-event size limit, the failed flag stays set and every subsequent body chunk observation returns this InvalidData error. It exists so the byte stream built in sse_stream_from_body keeps erroring instead of silently resuming mid-event after the real failure. Seeing it means the stream already produced the primary 'MCP response body exceeds N bytes' error earlier.

Source

Thrown at codex-rs/rmcp-client/src/http_client_adapter.rs:999

    previous_was_carriage_return: bool,
    failed: bool,
}

impl SseEventSizeLimit {
    fn new(maximum_bytes: Option<usize>) -> Self {
        Self {
            maximum_bytes,
            retained_bytes: 0,
            line_bytes: 0,
            line_is_comment: false,
            previous_was_carriage_return: false,
            failed: false,
        }
    }

    fn observe(&mut self, bytes: &[u8]) -> io::Result<()> {
        if self.failed {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "oversized MCP SSE event was already rejected",
            ));
        }
        let Some(maximum_bytes) = self.maximum_bytes else {
            return Ok(());
        };

        for &byte in bytes {
            if self.previous_was_carriage_return {
                self.previous_was_carriage_return = false;
                if byte == b'\n' {
                    continue;
                }
            }

            match byte {
                b'\r' => {

View on GitHub (pinned to 339751715c)

Solutions

  1. Treat any error from the SSE stream as terminal: stop polling, drop the connection, reconnect
  2. Find the first 'MCP response body exceeds N bytes' error earlier in the same stream/log — that is the root cause
  3. Fix the server so no single event exceeds the limit
  4. If you aggregate errors, deduplicate consecutive stream errors to avoid alarm noise

Example fix

// before
while let Some(item) = event_stream.next().await { /* logs a cascade of 'already rejected' */ }

// after
while let Some(item) = event_stream.next().await {
    if item.is_err() { break; } // first error is fatal; reconnect
}
Defensive patterns

Strategy: try-catch

Type guard

fn is_already_rejected(error: &std::io::Error) -> bool {
    error.kind() == std::io::ErrorKind::InvalidData
        && error.to_string().contains("already rejected")
}

Try / catch

// sticky failure: first error (any error) is terminal for the stream
while let Some(item) = event_stream.next().await {
    let item = match item {
        Ok(item) => item,
        Err(error) => {
            warn!("SSE stream failed ({error}); dropping connection");
            break; // reconnect rather than keep polling
        }
    };
}

Prevention

When it happens

Trigger: Any bytes arriving on the HTTP body stream after an earlier check_limit() failure — e.g. the caller keeps polling the SSE stream after the first oversize error and the server keeps sending the remainder of the huge event.

Common situations: Drain loops that log every SSE item and show a cascade of these errors; retry logic polling a dead stream; reading logs where the first real error scrolled past and only these remain visible.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/3ce88148d15d2127. Report an issue: GitHub.