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
- Check the `error` text in the message for the underlying serde deserialization failure
- Pin or update the Anthropic API version header so client and wire format match
- Validate the event JSON against the expected event schema (e.g. `content_block_start` requires `index` and `content_block`)
- 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
- Pin the Anthropic API version header (`anthropic-version`)
- Add new upstream event types to `is_known_sse_type` promptly
- Validate fixtures against current Anthropic event schemas
- Watch provider changelogs for required-field changes
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
- invalid SSE JSON
- PythonRuntime::with_state_path is deprecated — use…
- unrecognized SSE event
- Anthropic stream error
- Chat Completions stream closed before [DONE] or…
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)