Skip to content

fix(a2a-client): surface plain JSON-RPC error responses to streaming calls#79

Draft
arkavo-com wants to merge 1 commit into
a2aproject:mainfrom
arkavo-ai:fix/sse-plain-json-error
Draft

fix(a2a-client): surface plain JSON-RPC error responses to streaming calls#79
arkavo-com wants to merge 1 commit into
a2aproject:mainfrom
arkavo-ai:fix/sse-plain-json-error

Conversation

@arkavo-com

Copy link
Copy Markdown

Problem

parse_sse_bytes discards any unterminated buffer at end-of-stream. A server that answers a streaming call (SendStreamingMessage, SubscribeToTask) with a plain JSON-RPC error envelope — Content-Type: application/json, no SSE framing — therefore produces an empty, successfully-closed stream, and the caller never sees the error. a2a-server itself responds this way for pre-stream errors (e.g. -32004 from SubscribeToTask on a terminal task), so the SDK's own client+server pair silently loses these errors today.

Fix

Track whether any SSE event boundary was observed; at end-of-stream with no SSE framing and a non-empty residual buffer, parse the buffer as a JsonRpcResponse envelope. A contained error is emitted through the same mapping as SSE-framed envelope errors; a contained result is delivered as a normal StreamResponse; anything else keeps the existing clean-end behavior. SSE streams with trailing partial events are unaffected.

Found by / verified with

Cross-SDK interop testing (a2a-conformance, scenario errors/unsupported-operation-32004; FINDINGS.md finding 3): the a2a-rs client vs. its own server previously read the -32004 envelope as an empty stream; with this branch the scenario passes. 5 new unit tests; cargo test --workspace 452 passed / 0 failed (baseline on main: 447/0); fmt + clippy clean.

Related spec note

The underlying ambiguity isn't this SDK's fault: §9.4.2 doesn't specify whether a pre-stream protocol error is delivered as plain JSON or as an SSE-framed event, and implementations have picked both. Happy to open a spec issue on a2aproject/A2A if maintainers think it's worth pinning down — this fix makes the client robust to either choice.

🤖 Generated with Claude Code

…calls (spec §9.4.2)

A server that answers a streaming call (SendStreamingMessage,
SubscribeToTask) with a plain application/json JSON-RPC error envelope —
as a2a-rs's own server does for pre-stream errors such as -32004
UnsupportedOperation — produces a body with no SSE event boundary.
parse_sse_bytes discarded the unterminated buffer at end-of-stream, so
the caller saw an empty, successfully-closed stream and the error was
silently swallowed.

Track whether any SSE-framed event boundary was observed; at
end-of-stream, if none was and residual bytes remain, parse them as a
JsonRpcResponse envelope: an error is surfaced through the same
envelope-error mapping as SSE-framed errors, a result is delivered as a
normal event, and anything else keeps the existing clean-end behavior.

Found by cross-SDK interop testing (a2a-conformance).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces fallback handling for non-SSE-framed plain JSON-RPC response envelopes when a streaming call fails before producing a stream. It adds a parse_plain_json_tail helper, tracks whether any SSE events were seen during streaming, and parses the residual buffer at the end of the stream if no SSE events were observed. Additionally, several unit tests are added to verify this behavior. A review comment suggests updating an error message in parse_plain_json_tail to refer to a 'JSON-RPC parse error' instead of an 'SSE parse error' to prevent confusion.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread a2a-client/src/jsonrpc.rs
let result = rpc_resp.result?;
match protojson_conv::from_value::<StreamResponse>(result) {
Ok(sr) => Some(Ok(sr)),
Err(e) => Some(Err(A2AError::internal(format!("SSE parse error: {e}")))),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since this function is specifically parsing a plain JSON-RPC response tail (non-SSE), the error message should refer to a JSON-RPC or plain JSON parse error rather than an SSE parse error to avoid confusion during debugging.

Suggested change
Err(e) => Some(Err(A2AError::internal(format!("SSE parse error: {e}")))),
Err(e) => Some(Err(A2AError::internal(format!("JSON-RPC parse error: {e}")))),

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.00990% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
a2a-client/src/jsonrpc.rs 99.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

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