agentscope-ai / agentscope-ai/agentscope-java

[Bug]:JSON-RPC streaming client reports "Request cancelled" after receiving final TaskStatusUpdateEvent

Aperta
#2,927 4 commenti 0 reazioni 0 assegnatari Vedi su GitHub
bug
Lingua principale
Java
Stelle
5.6k
Fork
1.3k
Merge medio
4g 12h
PR unite (30g)
77

Descrizione

## Description

When using the JSON-RPC streaming client with `a2a-java-sdk` version `0.3.3.Final`, the error callback is invoked with:

```text
Request cancelled

after the client has already received a valid terminal TaskStatusUpdateEvent with final=true.

The remote A2A task completes successfully, but the client reports the normal end of the stream as an error.

## Environment

- a2a-java-sdk: 0.3.3.Final
- Transport: JSON-RPC over SSE
- HTTP client: JdkA2AHttpClient
- Java: 21
- A2A method: message/stream
- Protocol version: A2A 0.3.x

Relevant dependencies:


io.github.a2asdk
a2a-java-sdk-client
0.3.3.Final


io.github.a2asdk
a2a-java-sdk-client-transport-jsonrpc
0.3.3.Final


io.github.a2asdk
a2a-java-sdk-http-client
0.3.3.Final

## Steps to reproduce

Create an A2A client with JSON-RPC streaming enabled:

ClientConfig config = ClientConfig.builder()
.setStreaming(true)
.setPolling(false)
.build();

Client client = Client.builder(agentCard)
.withTransport(
JSONRPCTransport.class,
new JSONRPCTransportConfig(new JdkA2AHttpClient()))
.clientConfig(config)
.build();

Send a streaming message:

client.sendMessage(
message,
List.of((event, card) -> {
System.out.println("EVENT: " + event);
}),
error -> {
System.out.println("ERROR: " + error);
},
null);

The server returns normal A2A SSE events and eventually sends:

{
"jsonrpc": "2.0",
"id": "request-id",
"result": {
"taskId": "task-id",
"status": {
"state": "completed",
"timestamp": "2026-09-01T06:19:29.2982548Z"
},
"contextId": "context-id",
"final": true,
"kind": "status-update"
}
}

The server logs confirm that the task completed successfully:

Agent execution completed successfully
Task completed: signal=onComplete

However, immediately after the final event, the client error callback receives:

Request cancelled

## Actual behavior

The callback sequence is effectively:

TaskStatusUpdateEvent(final=true)
errorHandler(Request cancelled)

The application therefore sees a successfully completed A2A task as a failed streaming request.

The issue does not occur when the same endpoint is called with curl -N. curl receives the terminal event and exits normally.

## Expected behavior

After receiving:

{
"kind": "status-update",
"final": true,
"status": {
"state": "completed"
}
}

the client should complete normally.

Expected callback behavior:

TaskStatusUpdateEvent(final=true)
completion callback

The error callback should not receive Request cancelled for an internally initiated stream shutdown after a valid final event.

## Suspected root cause

SSEEventListener.handleMessage() appears to cancel the HTTP future after delivering a final status event:

eventHandler.accept(streamingEvent);

if (streamingEvent instanceof TaskStatusUpdateEvent statusUpdate
&& statusUpdate.isFinal()) {
future.cancel(true);
}

Cancelling the JDK HTTP client's future causes the HTTP layer to report:

Request cancelled

The resulting exception is then forwarded by SSEEventListener.onError():

public void onError(Throwable error, Future future) {
if (errorHandler != null) {
errorHandler.accept(error);
}
future.cancel(true);
}

The client therefore does not distinguish between:

1. an internal cancellation after receiving final=true;
2. a user-initiated cancellation;
3. an actual network or transport failure.

The completed flag also appears to be set only from onComplete(), not when a final TaskStatusUpdateEvent is received.

## Suggested fix

Mark the listener as successfully completed before cancelling the HTTP future, and suppress the cancellation exception caused by this internal
cleanup.

For example:

private volatile boolean completed;

private void handleMessage(JsonNode message, Future future) {
StreamingEventKind event = parseEvent(message);

eventHandler.accept(event);

if (event instanceof TaskStatusUpdateEvent statusUpdate
&& statusUpdate.isFinal()) {
completed = true;
future.cancel(true);
signalSuccessfulCompletion();
}
}

public void onError(Throwable error, Future future) {
if (completed && isCancellation(error)) {
return;
}

if (errorHandler != null) {
errorHandler.accept(error);
}
}

Alternatively, the client could wait for the server to close the SSE connection naturally instead of cancelling the future after receiving
final=true.

## Workaround

At the application layer, we currently ignore Request cancelled only when:

- a final event has already been received; or
- valid response artifacts have already been received.

A cancellation received before any valid response is still treated as a real error.

This workaround prevents successful A2A streaming tasks from being reported as failures, but the behavior would be better fixed inside the SDK.

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.