Summary
A2AClient.DeleteTaskPushNotificationConfigAsync always throws A2AException: "Failed to deserialize JSON-RPC result." against a spec-compliant server — including the SDK's own server. The delete succeeds on the server; the client cannot consume the (valid) empty result. DeleteTaskPushNotificationConfig is the only A2A method with a void result, so it is the only one affected — every other method returns a non-null result and round-trips correctly.
Environment
- Packages:
A2A, A2A.AspNetCore, A2A.V0_3Compat — all 1.0.0-preview2
- Transport: JSON-RPC over HTTP (v1.0 / proto-style)
- .NET 10
Root cause — client requires a non-null result
The generic send helper unconditionally deserializes result and throws when it is null:
// src/A2A/Client/A2AClient.cs:149-150
return rpcResponse.Result.Deserialize<TResult>(A2AJsonUtilities.DefaultOptions)
?? throw new A2AException("Failed to deserialize JSON-RPC result.", A2AErrorCode.InternalError);
Delete routes through that helper with TResult = object, so there is no path that tolerates a null result:
// src/A2A/Client/A2AClient.cs:92-95
public async Task DeleteTaskPushNotificationConfigAsync(DeleteTaskPushNotificationConfigRequest request, CancellationToken cancellationToken = default)
{
await SendJsonRpcRequestAsync<object>(A2AMethods.DeleteTaskPushNotificationConfig, request, cancellationToken).ConfigureAwait(false);
}
The server produces exactly that null result
Both server-side processors return a successful JSON-RPC response whose result is null for delete:
// src/A2A.AspNetCore/A2AJsonRpcProcessor.cs:159-162
case A2AMethods.DeleteTaskPushNotificationConfig:
var deletePnConfig = DeserializeAndValidate<DeleteTaskPushNotificationConfigRequest>(parameters.Value);
await requestHandler.DeleteTaskPushNotificationConfigAsync(deletePnConfig, cancellationToken).ConfigureAwait(false);
response = JsonRpcResponse.CreateJsonRpcResponse(requestId, (object?)null);
break;
(src/A2A.V0_3Compat/V03ServerProcessor.cs does the same in its v1.0 dispatch.)
A null result on a successful response is valid per JSON-RPC 2.0 — result MUST be present on success, and null is a legal value. So the server is correct and the client rejects its own server's valid response.
Reproduction
- Host any server that implements
DeleteTaskPushNotificationConfigAsync (the SDK's own A2AServer once a push-config store is wired in, or a custom IA2ARequestHandler).
- Create a task, register a push config, then call from the client:
await client.DeleteTaskPushNotificationConfigAsync(
new DeleteTaskPushNotificationConfigRequest { TaskId = taskId, Id = configId });
Expected
Completes without throwing. The config is deleted (and it is, server-side).
Actual
Throws:
A2A.A2AException: Failed to deserialize JSON-RPC result.
Observed wire response (server → client)
{"jsonrpc":"2.0","id":"<id>"}
i.e. a success response with no/null result. The client then evaluates rpcResponse.Result.Deserialize<object>() ?? throw, which throws.
CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, and ListTaskPushNotificationConfig round-trip fine over the same client/server pair; only delete fails.
Suggested fix (client side)
Give void-result methods a path that does not demand a non-null result. Any of:
- Add a no-result overload, e.g.
SendJsonRpcRequestAsync(string method, object? @params, CancellationToken) that sends the request, surfaces a JSON-RPC error if present, and returns without deserializing result. Have DeleteTaskPushNotificationConfigAsync call it.
- In the generic helper, when
TResult permits null (or the JSON result is null/absent) and no error is present, return default instead of throwing.
Either keeps the spec-compliant null-result response from being treated as a failure. (Server side is already correct; no change needed there.)
Notes
- Discovered while backing the push-config store methods (which
1.0.0-preview2 answers with -32003) via an IA2ARequestHandler decorator. With the store wired in, create/get/list work end-to-end; delete is the lone exception due to this client-side check.
Summary
A2AClient.DeleteTaskPushNotificationConfigAsyncalways throwsA2AException: "Failed to deserialize JSON-RPC result."against a spec-compliant server — including the SDK's own server. The delete succeeds on the server; the client cannot consume the (valid) empty result.DeleteTaskPushNotificationConfigis the only A2A method with a void result, so it is the only one affected — every other method returns a non-null result and round-trips correctly.Environment
A2A,A2A.AspNetCore,A2A.V0_3Compat— all1.0.0-preview2Root cause — client requires a non-null result
The generic send helper unconditionally deserializes
resultand throws when it is null:Delete routes through that helper with
TResult = object, so there is no path that tolerates a null result:The server produces exactly that null result
Both server-side processors return a successful JSON-RPC response whose
resultis null for delete:(
src/A2A.V0_3Compat/V03ServerProcessor.csdoes the same in its v1.0 dispatch.)A null
resulton a successful response is valid per JSON-RPC 2.0 —resultMUST be present on success, andnullis a legal value. So the server is correct and the client rejects its own server's valid response.Reproduction
DeleteTaskPushNotificationConfigAsync(the SDK's ownA2AServeronce a push-config store is wired in, or a customIA2ARequestHandler).Expected
Completes without throwing. The config is deleted (and it is, server-side).
Actual
Throws:
Observed wire response (server → client)
{"jsonrpc":"2.0","id":"<id>"}i.e. a success response with no/null
result. The client then evaluatesrpcResponse.Result.Deserialize<object>() ?? throw, which throws.CreateTaskPushNotificationConfig,GetTaskPushNotificationConfig, andListTaskPushNotificationConfiground-trip fine over the same client/server pair; only delete fails.Suggested fix (client side)
Give void-result methods a path that does not demand a non-null result. Any of:
SendJsonRpcRequestAsync(string method, object? @params, CancellationToken)that sends the request, surfaces a JSON-RPCerrorif present, and returns without deserializingresult. HaveDeleteTaskPushNotificationConfigAsynccall it.TResultpermits null (or the JSONresultis null/absent) and noerroris present, returndefaultinstead of throwing.Either keeps the spec-compliant null-result response from being treated as a failure. (Server side is already correct; no change needed there.)
Notes
1.0.0-preview2answers with-32003) via anIA2ARequestHandlerdecorator. With the store wired in, create/get/list work end-to-end; delete is the lone exception due to this client-side check.