Hmbown/CodeWhale · error · anyhow::Error

unrecognized SSE event

Error message

unrecognized SSE event: {error}

What it means

Raised by `convert_anthropic_sse_data` when the SSE data line is valid JSON but its `type` does not match any event type the client knows how to decode (the typed decode failed and the value is not an unknown-but-tolerable type, or there is no `type` field). This is the legacy error preserved for decode failures of recognized/shapeless events.

Solutions

  1. Check the `error` text in the message for the underlying serde deserialization failure
  2. Pin or update the Anthropic API version header so client and wire format match
  3. Validate the event JSON against the expected event schema (e.g. `content_block_start` requires `index` and `content_block`)
  4. If a new upstream event type appears, add it to `is_known_sse_type` so it is silently tolerated

Example fix

// before
{"type":"content_block_delta","delta":{"text":"hi"}}
// after
delta must carry its discriminator: {"type":"content_block_delta","delta":{"type":"text_delta","text":"hi"}}
Defensive patterns

Strategy: try-catch

Validate before calling

fn event_shape_ok(v: &serde_json::Value) -> bool {
    v.get("type").and_then(|t| t.as_str()).is_some()
}

Type guard

fn has_event_type(v: &serde_json::Value) -> Option<&str> {
    v.get("type").and_then(|t| t.as_str())
}

Try / catch

match result {
    Ok(event) => handle(event),
    Err(e) if e.to_string().contains("unrecognized SSE event") => {
        // inspect the serde sub-error embedded in the message
        log_raw_event(&raw); retry_or_fail(e);
    }
    Err(e) => retry_or_fail(e),
}

Prevention

When it happens

Trigger: A JSON SSE frame whose `type` string is a known Anthropic type but whose body fails typed deserialization (missing/wrongly-typed fields), or a frame with no `type` field at all.

Common situations: Anthropic API version drift introducing new required fields; a broken fixture; a middleware injecting extra JSON that shifts field types (e.g. numbers as strings).

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/533ecf08d5847e74. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/client/anthropic.rs:901

    if usage_event {
        return convert_anthropic_sse_usage_event(trimmed);
    }
    match serde_json::from_str::<StreamEvent>(trimmed) {
        // Local-only receipt: the legacy path ignored it (not a provider
        // type), so it stays ignored rather than decoding.
        Ok(StreamEvent::ToolProjectionWarning { .. }) => None,
        Ok(event) => Some(Ok(event)),
        Err(error) => {
            // Cold path, reached only when direct decode fails: invalid JSON
            // and unknown types keep their exact legacy outcomes.
            let value: Value = match serde_json::from_str(trimmed) {
                Ok(value) => value,
                Err(e) => return Some(Err(anyhow::anyhow!("invalid SSE JSON: {e}"))),
            };
            match value.get("type").and_then(Value::as_str) {
                // Tolerate unknown event types (e.g. future additions) silently.
                Some(known) if !is_known_sse_type(known) => None,
                _ => Some(Err(anyhow::anyhow!("unrecognized SSE event: {error}"))),
            }
        }
    }
}

/// Legacy `Value` path for `message_start`/`message_delta`: the usage
/// rewrite reads wire fields the normalized [`Usage`] cannot represent, so
/// these two events normalize before decoding, exactly as before.
fn convert_anthropic_sse_usage_event(trimmed: &str) -> Option<Result<StreamEvent>> {
    let mut value: Value = match serde_json::from_str(trimmed) {
        Ok(value) => value,
        Err(e) => return Some(Err(anyhow::anyhow!("invalid SSE JSON: {e}"))),
    };

    match value.get("type").and_then(Value::as_str) {
        Some("message_start") => {
            if let Some(usage) = value
                .get_mut("message")

View on GitHub (pinned to 73e0f67d83)