You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
cancel() and producer-failure write a terminal state to the store but not to active subscriber streams #1175
ActiveTask.cancel() now closes a task out to a terminal CANCELED state when
the executor's cancel() is cleanup-only or the task was parked in a
non-terminal state (e.g. input-required). That terminal state is written
directly to the task store via save_task_event, because by that point both
the agent and subscriber event queues are already closed.
The consequence is that the terminal state reaches the store but not an active subscriber stream. A client that was streaming the task
(SubscribeToTask / message/stream) receives the initial Task in WORKING
and then the stream ends; it never sees a CANCELED event and has to re-read
the task to learn the outcome. The push path does not fire either, since _update_task_state only notifies on PushNotificationEvent.
At the point of the direct store write, both queues are already closed, so a
store write is the only option left. The same pattern already exists for the
producer-failure path (active_task.py, the FAILED write in _run_producer's except Exception arm), under a comment that says so. #1172
kept scope to the authorization fix (#1159) and the "cancel leaves an actionable
terminal state" fix (#1170); this stream-visibility gap is orthogonal.
Proposed follow-up
Give a streaming or push-subscribed client a way to observe the terminal state
without polling. Options to weigh:
Emit a synthetic terminal TaskStatusUpdateEvent to _event_queue_subscribers
before the queues are torn down, so an active subscriber sees the transition.
Route the terminal store write through the push path so push_sender fires.
Both the cancel() CANCELED write and the producer-failure FAILED write share
this gap and should get the same treatment.
Repro
Subscribe to a live task whose executor's cancel() is cleanup-only, cancel it,
and collect the events the subscriber receives: the stream yields the initial WORKING task and ends, while task_store.get afterwards returns CANCELED.
Long-lived A2A clients that subscribe (message/stream / SubscribeToTask) need to observe a terminal outcome without polling. Today cancel() and the producer-failure path write CANCELED/FAILED to the store after both queues are closed, so a subscriber sees WORKING and then a hung-up stream.
I'll emit a synthetic terminal TaskStatusUpdateEvent to _event_queue_subscribersbefore teardown so the store write and the stream stay aligned, and check whether that same emit can feed push_sender. Tests for both cancel() and the producer-failure arm, with a subscriber attached.
cancel() emits CANCELED to the live subscriber stream and push before teardown. Producer-failure persists FAILED and fires push before close; the stream still gets the producer exception (blocking on_message_send would otherwise treat a FAILED Task as success).
Hi @chopmob-cloud , thanks for separating the issue. PR #1191 seems to fix the issue. Thanks @anthonyrs06 for working on this.
As a workaround till then you can wrap your streaming loop with a terminal event gaurd, such that when the stream disconnects without a terminal status event ,the client can perform a fallback get_task() query to read the final state from the database.
Also , If you control the server and want your active clients to see cancellations immediately, you can emit the terminal event inside your custom AgentExecutor.cancel() method before returning:
Rebased #1191 onto current main (after #1172) and addressed review.
Scope after this PR
cancel(): a live subscriber gets CANCELED (and push fires) before teardown. No poll required.
Producer-failure: store + push get FAILED before close; the stream still gets the producer exception, not a FAILED status event. A streaming client that survives the exception still cannot distinguish crash from disconnect without get_task(). That leftover is the blocking path treating a FAILED Task as success — filed as blocking on_message_send treats a FAILED Task as a successful result #1228 rather than baking the workaround into the contract.
Also: the helper now returns early when the task is already terminal, so a later failure cannot emit a duplicate push carrying the earlier state.
Activity
anthonyrs06 commentedon Aug 18, 2026
I'll take the follow-up that #1172 scoped out.
Long-lived A2A clients that subscribe (
message/stream/SubscribeToTask) need to observe a terminal outcome without polling. Todaycancel()and the producer-failure path writeCANCELED/FAILEDto the store after both queues are closed, so a subscriber seesWORKINGand then a hung-up stream.I'll emit a synthetic terminal
TaskStatusUpdateEventto_event_queue_subscribersbefore teardown so the store write and the stream stay aligned, and check whether that same emit can feedpush_sender. Tests for bothcancel()and the producer-failure arm, with a subscriber attached.anthonyrs06 commentedon Aug 18, 2026
Opened #1191 for this.
cancel()emitsCANCELEDto the live subscriber stream and push before teardown. Producer-failure persistsFAILEDand fires push before close; the stream still gets the producer exception (blockingon_message_sendwould otherwise treat aFAILEDTask as success).rohityan commentedon Aug 19, 2026
Hi @chopmob-cloud , thanks for separating the issue. PR #1191 seems to fix the issue. Thanks @anthonyrs06 for working on this.
As a workaround till then you can wrap your streaming loop with a terminal event gaurd, such that when the stream disconnects without a terminal status event ,the client can perform a fallback
get_task()query to read the final state from the database.Also , If you control the server and want your active clients to see cancellations immediately, you can emit the terminal event inside your custom
AgentExecutor.cancel()method before returning:refactor(server): drop redundant handler owner guards now enforced in…
github-actions commentedon Sep 2, 2026
Marking this issue as stale since it has been open for 14 days with no activity. This issue will be closed if no further activity occurs.
fix: publish terminal task state to subscriber streams (a2aproject#1175)
anthonyrs06 commentedon Sep 3, 2026
Rebased #1191 onto current main (after #1172) and addressed review.
Scope after this PR
cancel(): a live subscriber getsCANCELED(and push fires) before teardown. No poll required.FAILEDbefore close; the stream still gets the producer exception, not a FAILED status event. A streaming client that survives the exception still cannot distinguish crash from disconnect withoutget_task(). That leftover is the blocking path treating a FAILEDTaskas success — filed as blocking on_message_send treats a FAILED Task as a successful result #1228 rather than baking the workaround into the contract.Also: the helper now returns early when the task is already terminal, so a later failure cannot emit a duplicate push carrying the earlier state.
github-actions commentedon Sep 17, 2026
Marking this issue as stale since it has been open for 14 days with no activity. This issue will be closed if no further activity occurs.
github-actions commentedon Sep 30, 2026
This issue was closed because it has been inactive for 27 days. Please post a new issue if you need further assistance. Thanks!