Skip to content

[Bug]: A2A-Version is only validated on message sends, not on the other eight operations #1035

Description

@omatheusmesmo

What happened?

Section 3.6.2 of the specification is a MUST on every request:

Agents MUST process requests using the semantics of the requested A2A-Version (matching Major.Minor). If the version is not supported by the interface, agents MUST return a VersionNotSupportedError.

Agents MUST interpret empty value as 0.3 version.

The SDK calls A2AVersionValidator.validateProtocolVersion at exactly two call sites in each transport, both of them message sends:

Transport Validated Not validated
transport/jsonrpc JSONRPCHandler onMessageSend, onMessageSendStream onGetTask, onListTasks, onCancelTask, onSubscribeToTask, and the four push notification config methods
transport/rest RestHandler sendMessage, sendStreamingMessage same
transport/grpc GrpcHandler sendMessage, sendStreamingMessage same

So a client sending an A2A-Version the interface does not support is correctly refused when it sends a message, and served normally when it calls GetTask, ListTasks, CancelTask, SubscribeToTask or any of the push notification config operations. Whether the version is enforced depends on which operation is invoked rather than on the interface, which is the thing 3.6.2 is written to prevent: a client can conclude from a successful GetTask that its version is accepted, then have the next SendMessage refused.

The good news is that all three bindings behave identically here, so this is one behaviour to change rather than a per binding divergence.

Suggested fix

Move the check to a place every operation passes through, for instance the request handler decorator chain that already wraps RequestHandler, so it cannot be forgotten when an operation is added. A test per binding that calls GetTask with an unsupported A2A-Version and expects VersionNotSupportedError would pin it.

Happy to send a PR if you agree with the direction.

Reproducer

Added to JSONRPCHandlerTest next to the existing version tests, using the same card and the same incompatible context as testVersionNotSupportedErrorOnMessageSend, so the only difference is the operation invoked:

@Test
public void testVersionNotSupportedErrorOnGetTask() throws Exception {
    AgentCard agentCard = cardWithProtocolVersion10();
    JSONRPCHandler handler = new JSONRPCHandler(agentCard, requestHandler, internalExecutor);
    taskStore.save(MINIMAL_TASK, false);

    ServerCallContext contextWithVersion = new ServerCallContext(
            UnauthenticatedUser.INSTANCE,
            Map.of("foo", "bar"),
            new HashSet<>(),
            "2.0"); // Incompatible version

    GetTaskRequest request = new GetTaskRequest("1", new TaskQueryParams(MINIMAL_TASK.id()));
    GetTaskResponse response = handler.onGetTask(request, contextWithVersion);

    assertInstanceOf(VersionNotSupportedError.class, response.getError());
    assertNull(response.getResult());
}

Run together with the existing message-send test, the existing one passes and this one fails. I am happy to open a PR with the test alone if that is a useful place to start.

One thing I noticed and did not chase

I first wrote the same test for onCancelTask. It reproduces the same gap, but the forked JVM never exits afterwards: the cancel path taps a queue and leaves a consumer thread alive. The pre-existing testOnCancelTaskSuccess completes in 0.25s, so something about a freshly constructed handler is involved. I left that test out rather than ship something that hangs the build, but it may be worth a look on your side.

Relevant log output

$ mvn -pl transport/jsonrpc test \
    -Dtest='JSONRPCHandlerTest#testVersionNotSupportedErrorOnGetTask+testVersionNotSupportedErrorOnMessageSend'

[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.259 s
        <<< FAILURE! -- in JSONRPCHandlerTest
[ERROR] JSONRPCHandlerTest.testVersionNotSupportedErrorOnGetTask -- Time elapsed: 0.218 s
        <<< FAILURE!
org.opentest4j.AssertionFailedError: Unexpected null value,
        expected: <org.a2aproject.sdk.spec.VersionNotSupportedError> but was: <null>

# the pre-existing testVersionNotSupportedErrorOnMessageSend passes in the same run

Call sites, for reference:

$ git grep -n "validateProtocolVersion" -- '*/src/main/java/*'
server-common/.../version/A2AVersionValidator.java:33:    public static void validateProtocolVersion(
transport/grpc/.../handler/GrpcHandler.java:202:            A2AVersionValidator.validateProtocolVersion(
transport/grpc/.../handler/GrpcHandler.java:398:            A2AVersionValidator.validateProtocolVersion(
transport/jsonrpc/.../handler/JSONRPCHandler.java:233:            A2AVersionValidator.validateProtocolVersion(
transport/jsonrpc/.../handler/JSONRPCHandler.java:290:            A2AVersionValidator.validateProtocolVersion(
transport/rest/.../handler/RestHandler.java:230:            A2AVersionValidator.validateProtocolVersion(
transport/rest/.../handler/RestHandler.java:298:            A2AVersionValidator.validateProtocolVersion(

Environment

  • a2a-java main at 4f718e12 (1.2.1.Final-SNAPSHOT), behaviour identical in 1.2.0.Final

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions