{"record":{"id":"17d420e74c3fbd44","repo":"Hmbown/CodeWhale","slug":"err","errorCode":null,"errorMessage":"{err}","messagePattern":"\\{err\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/client/anthropic.rs","lineNumber":344,"sourceCode":"                            stream_idle_timeout,\n                            bytes_received,\n                            stream_start.elapsed(),\n                            last_chunk_at.elapsed(),\n                        )));\n                        return;\n                    }\n                };\n\n                bytes_received += chunk.len();\n                last_chunk_at = std::time::Instant::now();\n                buffer.extend_from_slice(&chunk);\n\n                loop {\n                    let line = match super::take_sse_line(&mut buffer) {\n                        Ok(Some(line)) => line,\n                        Ok(None) => break,\n                        Err(err) => {\n                            yield Err(anyhow::anyhow!(\"{err}\"));\n                            return;\n                        }\n                    };\n\n                    // `event:` lines are redundant (the data payload carries\n                    // `type`) and comment/heartbeat lines are ignorable.\n                    let Some(data) = super::extract_sse_data_value(&line) else {\n                        continue;\n                    };\n\n                    match convert_anthropic_sse_data(data) {\n                        Some(Ok(StreamEvent::Error { error })) => {\n                            let (error_type, message) = anthropic_error_fields(&error);\n                            yield Err(anyhow::anyhow!(\n                                \"Anthropic stream error ({error_type}): {message}\"\n                            ));\n                            return;\n                        }","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/client/anthropic.rs#L326-L362","documentation":"The Anthropic SSE reader decodes only complete lines and decodes them strictly: a line containing bytes that are not valid UTF-8 produces this error instead of silently substituting U+FFFD replacement characters (a deliberate contract, tracked as #5374). `take_sse_line` splits on `\\n`, strips a trailing `\\r`, then runs `std::str::from_utf8`; on failure the `InvalidSseUtf8` error (with `valid_up_to`/`error_len`) is surfaced and the stream terminates.","triggerScenarios":"A gateway/MITM proxy re-encoding or corrupting the event stream (e.g. broken chunked-compression, charset transcoding); a provider bug emitting binary garbage inside a `data:` line; a misbehaving cache layer serving mixed-encoding fragments. Because line splitting happens on `\\n` bytes, normal multi-byte text split across reads is NOT a cause — only genuinely invalid byte sequences trigger it.","commonSituations":"Antivirus/SSL-inspection appliances rewriting SSE bodies; reverse proxies mis-applying gzip twice; self-hosted anthropic-compatible servers writing log bytes into the stream; extremely rare provider-side serialization bugs.","solutions":["Bypass the intermediary: connect directly to the API endpoint without the SSL-inspecting proxy/VPN and retry.","Fix proxy configuration: disable response buffering/transcoding for `text/event-stream` and ensure compression is applied once.","If you control the server, audit what is written into the stream — the error means raw invalid bytes on a complete line.","Report persistent occurrences to the provider with a timestamp; strict rejection here is intentional so corruption is visible rather than silently mangled."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_invalid_sse_utf8_error(e: &anyhow::Error) -> bool {\n    e.to_string().contains(\"invalid UTF-8\") || e.to_string().contains(\"valid up to\")\n}","tryCatchPattern":"// The stream terminates on invalid UTF-8 by design; surface the cause,\n// do not swallow it into replacement characters.\nmatch stream.next().await {\n    Some(Err(e)) if is_invalid_sse_utf8_error(&e) => {\n        log::error!(\"SSE stream corrupted (invalid UTF-8) — bypass SSL-inspecting proxies\");\n        return Err(e); // keep corruption visible instead of mangling output\n    }\n    other => handle(other),\n}","preventionTips":["Exclude API domains from SSL-inspection/antivirus appliances that rewrite event-stream bodies.","Ensure reverse proxies apply content-encoding exactly once and disable SSE buffering/transcoding.","Treat this error as an infrastructure signal, not a data loss — strict rejection exists so corruption is never silently masked."],"tags":["anthropic","streaming","utf-8","sse","proxy","data-corruption"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}