Hmbown/CodeWhale · error · anyhow::Error
invalid SSE JSON
Error message
invalid SSE JSON: {e} What it means
This error is raised by `convert_anthropic_sse_data` in the Anthropic SSE client when a raw `data:` line from the stream cannot be parsed as JSON by the fallback legacy path. The function first tries a typed decode; on failure it attempts `serde_json::from_str`, and if even that fails, the line is not valid JSON at all, so the stream yields a hard error and terminates.
Solutions
- Log the raw `data:` line before parsing to inspect the exact malformed payload
- Check whether a proxy/gateway is truncating or rewriting SSE chunks and bypass or fix it
- Verify the base URL points at the real Anthropic Messages API
- Fix the malformed fixture if this comes from a test fixture
Example fix
// before: fixture with truncated JSON
{"type":"content_block_delta","delta":{"type":"text_delta","text":"hi"
// after: complete JSON object per SSE data line
{"type":"content_block_delta","delta":{"type":"text_delta","text":"hi"}} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-validate a fixture line before feeding the SSE parser
fn is_valid_json_line(line: &str) -> bool {
serde_json::from_str::<serde_json::Value>(line.trim()).is_ok()
} Type guard
fn parse_sse_json(line: &str) -> Option<serde_json::Value> {
serde_json::from_str(line.trim()).ok()
} Try / catch
match result {
Ok(event) => handle(event),
Err(e) if e.to_string().contains("invalid SSE JSON") => {
log_raw_line(&raw); // capture payload for diagnosis
fail_stream(e);
}
Err(e) => fail_stream(e),
} Prevention
- Log raw SSE lines at debug level before parsing
- Test SSE fixtures with a JSON schema validator
- Bypass proxies when diagnosing to rule out chunk mangling
- Keep base URL pointed at the genuine Anthropic Messages API
When it happens
Trigger: An Anthropic SSE `data:` frame contains malformed JSON — e.g. truncated frames from a proxy buffering bug, a mock/test fixture with bad JSON, or a non-Anthropic endpoint behind an Anthropic-compatible base URL emitting non-JSON payloads.
Common situations: Corporate proxies or gateways that mangle chunked responses; pointing the client at an OpenAI-style or self-hosted endpoint that emits malformed lines; hand-written SSE fixtures in tests with typos in the JSON.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
- PythonRuntime::with_state_path is deprecated — use…
- unrecognized SSE event
- Anthropic stream error
- Antigravity cloud-code SSE is not JSON
- Chat Completions stream closed before [DONE] or…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/e8c2aaa541265dc9.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/client/anthropic.rs:896
}
let usage_event = matches!(
serde_json::from_str::<SseTagPeek>(trimmed).map(|peek| peek.r#type),
Ok(Some("message_start" | "message_delta"))
);
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}"))),
};View on GitHub (pinned to 73e0f67d83)