Skip to content

fix(anthropic): emit proper SSE error events on streaming requests - #2271

Merged
steebchen merged 1 commit into
theopenco:mainfrom
RATCHAW:fix/anthropic-streaming-error-events
May 12, 2026
Merged

steebchen merged 1 commit into
theopenco:mainfrom
RATCHAW:fix/anthropic-streaming-error-events

Conversation

@RATCHAW

@RATCHAW RATCHAW commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Fixes a bug where /v1/messages streaming requests ended with only event: message_stop when validation failed upstream, instead of emitting a proper event: error per the Anthropic streaming protocol.
  • The Anthropic-format streaming handler now tracks SSE event: lines from the internal /v1/chat/completions stream, detects error chunks (by event type or shape), and translates them into Anthropic event: error events followed by event: message_stop.
  • Fixes the !response.ok branch so that when stream: true was requested it returns SSE instead of a non-streaming JSON body, preserving the SSE contract.
  • Error type mapping keeps canonical Anthropic types verbatim on the passthrough path (the docs note the enum will grow) and only remaps internal finish reasons: client_error, gateway_error, upstream_error.

Reproduction

Malformed payload from the bug report: assistant tool_use followed by a user message instead of tool_result:

curl -sS -N -X POST http://localhost:4001/v1/messages \
	-H "Content-Type: application/json" \
	-H "x-api-key: test-token" \
	-H "anthropic-version: 2023-06-01" \
	-d '{
		"model": "claude-sonnet-4-6",
		"messages": [
			{ "role": "user", "content": "Hello" },
			{
				"role": "assistant",
				"content": [
					{
						"type": "tool_use",
						"id": "toolu_01TEST123",
						"name": "read",
						"input": { "path": "/test" }
					}
				]
			},
			{ "role": "user", "content": "Hi" }
		],
		"max_tokens": 100,
		"stream": true
	}'

Before

event: message_stop
data: {"type":"message_stop"}

After

event: error
data: {"type":"error","error":{"type":"invalid_request_error","message":"messages.2: `tool_use` ids were found without `tool_result` blocks immediately after: toolu_01TEST123. Each `tool_use` block must have a corresponding `tool_result` block in the next message."}}

event: message_stop
data: {"type":"message_stop"}

Test plan

  • Unit tests for buildAnthropicErrorEvent and mapInternalErrorTypeToAnthropic (11 cases): exact repro shape, wrapped internal chat.ts shape, canonical Anthropic types, unknown/future types, edge cases.
  • pnpm --filter gateway build
  • pnpm --filter gateway lint
  • pnpm format
  • Manual curl repro against local gateway; output matches After above.

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced error handling for streaming responses with improved error detection and standardized error formatting.
    • Streaming errors now emit standardized error events with proper message termination.
  • Tests

    • Added comprehensive test coverage for error translation and response standardization logic.

Review Change Stack

When /v1/messages received an upstream provider error (for example an
Anthropic 400 for a tool_use / tool_result mismatch) on a streaming
request, the gateway ended the stream with only `event: message_stop`,
which breaks the Anthropic streaming contract and hides the real error.

The streaming handler now tracks SSE event types from the internal chat
completions stream, detects error chunks (by event type or payload
shape), and emits Anthropic-style `event: error` payloads followed by
`event: message_stop`. The `!response.ok` path now returns SSE when the
client asked for streaming instead of a JSON error body.
@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c205301a-25c5-43ac-8abd-8c84b7fcd5d6

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc157a and 50a64ea.

📒 Files selected for processing (3)
  • apps/gateway/src/anthropic/anthropic.ts
  • apps/gateway/src/anthropic/streaming-error-translation.spec.ts
  • apps/gateway/src/anthropic/streaming-error-translation.ts

Walkthrough

This PR adds Anthropic-specific error translation and integrates it into the streaming response handler. A new streaming-error-translation.ts module defines canonical error types and provides functions to map internal error categories to Anthropic types and build normalized error events. These utilities are then integrated into anthropic.ts to detect upstream streaming errors, emit proper error SSE events, and gracefully terminate the stream.

Changes

Anthropic Streaming Error Handling

Layer / File(s) Summary
Error translation utilities
apps/gateway/src/anthropic/streaming-error-translation.ts
Defines a canonical set of Anthropic error.type strings and exports mapInternalErrorTypeToAnthropic to convert internal error categories (e.g., client_error, gateway_error, upstream_error) to Anthropic types, defaulting to api_error. Exports buildAnthropicErrorEvent to normalize unknown error payloads into { type: "error", error: { type, message } } objects, handling passthrough of valid Anthropic shapes, translation of wrapped internal errors, and fallback to api_error with stringified messages.
Streaming error handling integration
apps/gateway/src/anthropic/anthropic.ts
Imports buildAnthropicErrorEvent and adds error handling when the upstream /v1/chat/completions request fails in streaming mode: parses the error response and emits an SSE error event followed by message_stop. In the SSE parser, introduces currentEventType tracking, normalizes line endings (removes trailing \r), resets event type on blank lines, and sets it when encountering event: lines. Adds looksLikeError logic to detect error payloads based on event type and structure, emitting an SSE error event and terminating the stream immediately.
Error translation test coverage
apps/gateway/src/anthropic/streaming-error-translation.spec.ts
Provides test suites for mapInternalErrorTypeToAnthropic verifying internal-to-Anthropic type mappings and api_error default behavior, and for buildAnthropicErrorEvent verifying passthrough of valid Anthropic errors, translation of wrapped internal error objects, and fallback behavior with JSON-stringified unparseable inputs and string/inner-error messages.

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(anthropic): emit proper SSE error events on streaming requests' directly and specifically describes the main change: correcting how Anthropic streaming requests emit error events in SSE format.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@steebchen
steebchen added this pull request to the merge queue May 12, 2026
@steebchen
steebchen removed this pull request from the merge queue due to a manual request May 12, 2026
@steebchen
steebchen merged commit b37de53 into theopenco:main May 12, 2026
11 checks passed
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.

2 participants