Skip to content

fix: prevent TransferEncodingError in Gemini/Claude SSE streams#2321

Closed
Stranmor wants to merge 1 commit into
lbjlaq:mainfrom
Stranmor:fix/sse-stream-transfer-encoding-error
Closed

fix: prevent TransferEncodingError in Gemini/Claude SSE streams#2321
Stranmor wants to merge 1 commit into
lbjlaq:mainfrom
Stranmor:fix/sse-stream-transfer-encoding-error

Conversation

@Stranmor

Copy link
Copy Markdown
Contributor

Problem

HTTP clients using aiohttp/httpx (e.g. OpenWebUI) receive:

TransferEncodingError: 400, message='Not enough data to satisfy transfer length header.'

Root Cause

When SSE streams encounter upstream errors (connection drop, timeout), the Gemini handler and Claude SSE mapper use yield Err(...) inside async_stream::stream!. This Err propagates through Body::from_stream(), causing hyper to abort HTTP/1.1 chunked transfer encoding without sending the final 0\r\n\r\n terminator chunk.

HTTP clients that validate chunked encoding integrity then throw TransferEncodingError.

Fix

Convert stream errors from Err(...) to Ok(Bytes) containing SSE-formatted error content. This matches the pattern already used by the OpenAI streaming handler (line ~282 in streaming.rs), which correctly wraps errors as Ok(error_json_bytes).

The stream terminates cleanly with None, hyper sends the proper 0\r\n\r\n terminator, and the client receives a well-formed HTTP response with error details.

Changes

File Before After
handlers/gemini.rs yield Err(format!("Stream error: {}", e)) yield Ok(Bytes::from(error_json_sse))
mappers/claude/mod.rs yield Err(format!("Stream error: {}", e)) yield Ok(Bytes::from(error_event_sse))

Already correct: OpenAI handler wraps errors as Ok(Bytes) with JSON error payload + [DONE].

When SSE streams encounter upstream errors, yield Err(...) causes hyper
to abort HTTP/1.1 chunked transfer encoding without the final terminator.
HTTP clients (aiohttp, httpx) then throw TransferEncodingError.

Fix: convert stream errors to Ok(Bytes) SSE content, matching the pattern
already used by the OpenAI streaming handler.

Affected: Gemini handler, Claude SSE mapper
Already correct: OpenAI handler (errors wrapped as Ok)
@Stranmor

Copy link
Copy Markdown
Contributor Author

CI Failures — Pre-existing, Not Caused by This PR

Check Rust Code (fmt): The cargo fmt --check failures are trailing whitespace in files not modified by this PR (request.rs, claude.rs, thinking_utils.rs, protobuf.rs). These formatting issues exist on main and would fail on any PR.

Build Tauri App (macOS/Windows): failed to decode secret key: incorrect updater private key password — this is a CI secrets configuration issue (TAURI_SIGNING_PRIVATE_KEY), unrelated to code changes.

This PR only modifies 2 files (gemini.rs, claude/mod.rs) with a 6-line diff (2 deletions, 6 insertions).

@Stranmor

Copy link
Copy Markdown
Contributor Author

Closing in favor of #2322 — it uses serde_json::json!() for proper JSON escaping instead of manual format!() + replace(), which is more robust.

@Stranmor Stranmor closed this Mar 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant