Skip to content

gRPC client ignores google.rpc.Status/ErrorInfo, collapsing all FAILED_PRECONDITION errors to -32002 (TaskNotCancelable) #103

Description

@janosSarusiKis

The a2a-grpc client maps incoming gRPC errors to A2A errors using only the
tonic::Status code. It never decodes the google.rpc.Status / ErrorInfo carried
in the grpc-status-details-bin trailer. Because A2A spec §5.4 maps three distinct
errors to the same gRPC status FAILED_PRECONDITION, the client cannot tell them
apart and unconditionally coerces all of them to -32002 (TaskNotCancelable):

A2A error JSON-RPC gRPC status
TaskNotCancelableError -32002 FAILED_PRECONDITION
PushNotificationNotSupportedError -32003 FAILED_PRECONDITION
UnsupportedOperationError -32004 FAILED_PRECONDITION

The specific error is meant to be recovered from the ErrorInfo detail
(reason = UPPER_SNAKE_CASE name, domain = a2a-protocol.org, AIP-193 rich error
model). The Go, Python, and .NET clients all read it; the Rust gRPC client does not.

Location

a2a-grpc/src/errors.rs, status_to_a2a_error (lines 29–40):

pub fn status_to_a2a_error(status: &tonic::Status) -> A2AError {
    let code = match status.code() {          // only the gRPC code is inspected
        tonic::Code::NotFound => error_code::TASK_NOT_FOUND,
        tonic::Code::FailedPrecondition => error_code::TASK_NOT_CANCELABLE,  // fixed -32002
        ...
    };
    A2AError::new(code, status.message())      // status.details() never read
}

Every gRPC client method routes errors through this function
(a2a-grpc/src/client.rs, .map_err(|s| status_to_a2a_error(&s))).
status.details() is never called anywhere in the crate.

Steps to reproduce
Call CreateTaskPushNotificationConfig (gRPC) against a server that does not support
push notifications. The server returns FAILED_PRECONDITION with a correct
ErrorInfo (reason=PUSH_NOTIFICATION_NOT_SUPPORTED, domain=a2a-protocol.org).

Expected: A2A code -32003 (PushNotificationNotSupported)
Actual: A2A code -32002 (TaskNotCancelable)
Observed in the agntcy-a2a interop matrix (Rust client → .NET server, gRPC). Go and
Python clients recover -32003 correctly from the same response.

Note
The SDK already has the logic to fix this — a2a-client/src/lib.rs
a2a_error_from_details() reads ErrorInfo, checks domain == "a2a-protocol.org",
and maps reason via reason_to_error_code(). It is wired into the JSON-RPC and REST
clients (jsonrpc.rs, rest.rs) but not the gRPC client. The gRPC path should
decode status.details() as google.rpc.Status, extract the ErrorInfo, and reuse
reason_to_error_code() — mirroring the other transports.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions