Skip to content

fix(ui): keep following a chat turn when its stream breaks - #3198

Open
garlicKim21 wants to merge 1 commit into
kagent-dev:mainfrom
garlicKim21:fix/ui-chat-stream-reattach
Open

garlicKim21 wants to merge 1 commit into
kagent-dev:mainfrom
garlicKim21:fix/ui-chat-stream-reattach

Conversation

@garlicKim21

Copy link
Copy Markdown
Contributor

Summary

Fixes #3197

When the chat stream (A2AService/SendStreamingMessage over gRPC-Web) breaks while the task is still running, the UI reports the turn as failed ("The agent could not finish this turn / network error"), even though the task keeps running on the server and completes, with its result in the task history.

This is easy to hit behind a proxy that closes quiet streams. A model reading a long prompt sends nothing for a minute or more, and an idle timeout somewhere on the path (an ingress, a reverse proxy, a CDN) ends the response. In our case the controller logged both streams as Canceled after exactly 60,015 ms. Both tasks finished one to two minutes after the cut, by which time the UI had already shown the error banner.

This change keeps following the task instead of failing the turn:

  • Once the stream has named its task (the first statusUpdate carries taskId), a transport failure re-attaches with SubscribeToTask. The gateway already implements it (go/core/internal/a2agateway/interactions.go). It is read-only and returns the stored task when the task has already finished, so the user's message is never sent twice.
  • Only transport failures are retried: Unknown (a bare network error, or a body that ends without its trailer) and Unavailable (HTTP 502/503/504). Errors the controller decided, a broken stream before any task id is known, a task that has already reached a terminal or input-required state, and a user abort behave as before.
  • Re-attaching gives up after three consecutive failures with no frame in between, so a dead path still ends the turn.

The change is confined to A2AGrpcChatClient.send; the event handling for each frame is unchanged, so a task snapshot returned by the subscription goes through the existing task branch.

Testing

  • New unit tests in ui/src/api/chat/a2aGrpcChatClient.test.ts:
    • a stream that breaks after naming its task re-attaches once and ends with the completed task and its answer
    • a stream that breaks before naming a task does not re-attach
    • an error with a server-decided code (FailedPrecondition) still surfaces and does not re-attach
    • repeated breaks stop after three re-attach attempts
  • The first and last tests fail without the change and pass with it.
  • yarn typecheck: passes.
  • yarn lint: 0 errors; the same 6 existing warnings as on main.
  • yarn test: 59 files, 598 tests pass.
  • Run in node:24.13.0 (ui/.nvmrc) with Corepack and yarn install --immutable, as in the ui-tests CI job. Browser (Playwright) tests were not run locally.

A proxy between the browser and the controller can close a stream that has
been quiet for a while, for example while a model reads a long prompt. The
task keeps running on the server and completes, but the UI reported the turn
as failed ("network error").

Once the stream has named its task, a transport failure (Unknown or
Unavailable) now re-attaches to that task with SubscribeToTask instead of
failing the turn. SubscribeToTask is read-only and returns the stored task
when it has already finished, so the message is never sent twice. Errors
the controller decided, a broken stream before any task is known, and a
user abort still end the turn as before. Re-attaching gives up after three
consecutive failures with no frame in between.

Signed-off-by: Golden Garlic <148346166+garlicKim21@users.noreply.github.com>
@github-actions github-actions Bot added the bug Something isn't working label Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] UI marks a chat turn as failed when a proxy idle timeout cuts the response stream, although the task completes

1 participant