Repository navigation
fix(client): raise A2A errors for malformed JSON-RPC responses - #1375
Merged
Iwaniukooo11 merged 2 commits intoOct 26, 2026
Merged
Iwaniukooo11 merged 2 commits into
Iwaniukooo11 merged 2 commits into
Conversation
JsonRpcTransport built json-rpc JSONRPC20Response objects from the raw response body, so a body that is not a JSON-RPC 2.0 response object leaked TypeError, ValueError or JSONDecodeError instead of an A2AClientError, and an error object with an extra member or without a message never reached the error-code mapping. Validate the envelope once in _send_request and in the SSE loop, raise the mapped A2A error straight from the error object, and raise A2AClientError for anything else.
🧪 Code Coverage (vs
|
| Base | PR | Delta | |
|---|---|---|---|
| src/a2a/client/transports/jsonrpc.py | 92.66% | 95.51% | 🟢 +2.86% |
| Total | 93.03% | 93.08% | 🟢 +0.05% |
Generated by coverage-comment.yml
Iwaniukooo11
pushed a commit
that referenced
this pull request
Oct 27, 2026
🤖 I have created a release *beep* *boop* --- ## [1.3.0](v1.2.3...v1.3.0) (2026-10-27) ### Features * **server:** release parked interrupted tasks after a configurable timeout ([#1338](#1338)) ([b22a68d](b22a68d)) ### Bug Fixes * **client:** Create separate httpx per client when none configured ([#1358](#1358)) ([d316d0e](d316d0e)) * **client:** raise A2A errors for malformed JSON-RPC responses ([#1375](#1375)) ([7e55413](7e55413)) * **client:** rejections from unrecognized fields ([#1354](#1354)) ([91929cd](91929cd)) * **compat:** keep an unset v0.3 status message and provider as None ([#1362](#1362)) ([cdb4799](cdb4799)), closes [#1361](#1361) * **compat:** return A2A error codes from the v0.3 JSON-RPC adapter ([#1369](#1369)) ([8e32d53](8e32d53)) * **compat:** set last_updated when storing a task in v0.3 format ([#1372](#1372)) ([30b6d50](30b6d50)), closes [#1371](#1371) * **core:** restore structured __repr__ on A2A exceptions ([#1348](#1348)) ([79a6f45](79a6f45)) * **server:** allow recovery after status event validation fails ([#1365](#1365)) ([ca3ef0d](ca3ef0d)) * **server:** apply history_length to legacy streaming sends ([#1364](#1364)) ([261c074](261c074)) * **server:** include legacy task states in database list filters ([#1337](#1337)) ([e12d494](e12d494)) * **server:** record executor failures through the event consumer ([#1366](#1366)) ([ad85806](ad85806)) * **server:** reject boolean JSON-RPC request IDs ([#1350](#1350)) ([05392b3](05392b3)) * skip malformed agent card signature headers ([#1335](#1335)) ([b21a144](b21a144)) ### Documentation * **helpers:** note that whole numbers in a data Part come back as floats ([#1373](#1373)) ([654774f](654774f)) ### Miscellaneous Chores * release 1.3.0 ([#1389](#1389)) ([fb6373f](fb6373f)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: mykytanetipa <mykytanetipa@google.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
JsonRpcTransportturned every response body into ajson-rpcJSONRPC20Response(JSONRPC20Response(**response_data)in the nine unary methods,JSONRPC20Response.from_jsonfor SSE events). That package validates the envelope with its own exceptions, so a body that is not a JSON-RPC 2.0 response object reached the caller as a rawTypeError,ValueErrororjson.JSONDecodeErrorinstead of anA2AClientError:TypeError: ... argument after ** must be a mappingA2AClientErrorresultnorerrorValueError: Either result or error should be usedA2AClientError"error": "boom"TypeError: ... argument after ** must be a mappingA2AClientError{"code": -32001, "message": "...", "reason": "x"}TypeError: JSONRPCError.__init__() got an unexpected keyword argument 'reason'TaskNotFoundErrormessage, e.g.{"code": -32001}ValueError: Error message should be stringTaskNotFoundErrordata:that is not JSONjson.JSONDecodeError(raised in the consumer loop, outsidehandle_http_exceptions)A2AClientErrorevent: errorwhose error object has an extra memberTypeErrorTaskNotFoundErrorThe last two error-object rows matter most: the server sent a valid A2A error code, and
_create_jsonrpc_erroralready handles a missingmessage(error_dict.get('message', str(error_dict))), but thejson-rpcvalidation failed before that code ran. It is also the error-object counterpart of the spec 5.7 leniency added for results in #1354.Change, all in
src/a2a/client/transports/jsonrpc.py:_check_jsonrpc_response: anerrorobject raises the mapped A2A error through the existing_create_jsonrpc_error; a non-object body, a non-objecterror, or a body with neitherresultnorerrorraisesA2AClientError. A falsyerrornext to aresultis still treated as success, as before._send_requestand the SSE loop call it, so the unary methods readresponse_data['result']and no longer need their own error checks._handle_sse_errorparses withjson.loadsand keeps itsSSE stream error: ...fallback.JSONRPC20Responseis no longer used on the client (requests still useJSONRPC20Request). Well-formed success and error responses behave as before; the existing tests pass unchanged.Tests:
TestMalformedJsonRpcResponsesintests/client/transports/test_jsonrpc_client.py, 11 cases (unary invalid envelopes, non-standard error objects, streamingdata:andevent: error, plus a guard thatdelete_task_push_notification_configstill raises on an error response). 10 of them fail onmainwith the exceptions in the table and pass with the change.The v0.3
CompatJsonRpcTransporthas the sameJSONRPC20Response(**response_data)pattern (an error object with an extra member raisesTypeErrorthere too). I kept this PR to the 1.0 client and can send afix(compat)follow-up.Checks, run on Windows 11 with Python 3.10.19:
bash ./scripts/lint.sh: ruff check, ruff format andtypass (three existing unusedtype: ignorewarnings in files this PR does not touch).uv run pytest tests/client/transports/test_jsonrpc_client.py: 51 passed.uv run pytest --ignore=tests/integration/cross_version --ignore=tests/integration/test_scenarios.py --cov=src: 2213 passed, 199 skipped, 3 xfailed; total coverage 93%. The 3 failures (test_samples_smoke.py, console encoding) and 4 setup errors (push_notificationse2e,Can't pickle local objectunder Windows spawn) fail the same way onmainhere.tests/integration/cross_version(usesos.killpg) andtests/integration/test_scenarios.py(hangs on Windows onmaintoo; it only builds gRPC clients) were not run locally, so the Linux CI run is the check for those.Follow the
CONTRIBUTINGGuide.Make your Pull Request title in the https://www.conventionalcommits.org/ specification.
Ensure the tests and linter pass
Appropriate docs were updated (if necessary)