agentscope-ai / agentscope-ai/agentscope-java

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

オープン
#2,927 コメント 4 件 リアクション 0 件 担当者 0 名 GitHub で見る
bug
主要言語
Java
スター
5.6k
フォーク
1.3k
平均マージ
4日 12時間
マージ済み PR(30日)
77

説明

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

コントリビューションガイド

コントリビューションガイドを開く

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。