agentscope-ai / agentscope-ai/agentscope-java

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

Offen
#2,927 4 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
bug
Vorherrschende Sprache
Java
Sterne
5.6k
Forks
1.3k
Ø Merge
4 T. 12 Std.
Gemergte PRs (30 T.)
77

Beschreibung

## 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.

Beitragsleitfaden

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.