fix: expose terminal task_updated events as typed TaskUpdatedMessage#1016
Open
maxim092001 wants to merge 1 commit into
Open
fix: expose terminal task_updated events as typed TaskUpdatedMessage#1016maxim092001 wants to merge 1 commit into
maxim092001 wants to merge 1 commit into
Conversation
Background tasks sometimes finish by emitting only a generic system/task_updated message whose patch.status is terminal, with no typed TaskNotificationMessage. Consumers that track active task IDs from TaskStartedMessage and clear them only on TaskNotificationMessage then believe a finished task is still active and can hang draining the persistent stream until an outer timeout. Expose system/task_updated as a typed TaskUpdatedMessage(SystemMessage) with task_id, patch, status, session_id, uuid — mirroring the TypeScript SDK's SDKTaskUpdatedMessage. Parsing is defensive (all .get(), non-dict patch guard, status derived from patch.status) so a lifecycle event can never raise. Add TaskUpdatedStatus and a shared TERMINAL_TASK_STATUSES frozenset so consumers can clear active task IDs on a terminal status from either TaskNotificationMessage or TaskUpdatedMessage, and document the lifecycle contract on both message types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Hey @qing-ant, could you please check it out? Thanks! |
sashabaranov
approved these changes
Jun 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1019
Summary
Background tasks can leave consumers with stale active-task state. A background Bash task sometimes finishes by emitting only a generic
system/task_updatedmessage whosepatch.statusis terminal (completed/failed/stopped), with no typedTaskNotificationMessage:Consumers that track active task IDs from
TaskStartedMessageand clear them only onTaskNotificationMessagethen believe a finished task is still active. If they use that active set to boundreceive_messages()(which is open-ended for the persistent client), they keep draining the stream until an outer timeout.The parser previously mapped
system/task_updatedto a genericSystemMessage, so terminal task state — semantically lifecycle data — was never represented by a typed lifecycle message. This breaks the contract: a typed start event (TaskStartedMessage) was not guaranteed a typed terminal event.Fix
Expose
system/task_updatedas a typedTaskUpdatedMessage(SystemMessage), mirroring the TypeScript SDK'sSDKTaskUpdatedMessage:task_iddata.task_idpatchdata.patch(full dict preserved)statuspatch.statussession_iddata.session_iduuiddata.uuidPlus:
TaskUpdatedStatus—Literal["running","completed","failed","stopped","cancelled"].TERMINAL_TASK_STATUSES— sharedfrozensetof finished statuses, so consumers can clear active task IDs on a terminal status from eitherTaskNotificationMessageorTaskUpdatedMessage.TaskUpdatedMessageandTaskNotificationMessage.After this change, a bounded drain works without hanging:
Defensive parsing
task_updatedparsing uses.get()throughout, guards a non-dictpatch, and derivesstatusfrompatch.status— it can never raise on a lifecycle event (the observed CLI payload omitsuuid/session_id). A patch carrying onlyend_time/result/erroris left non-terminal (status=None) with the fullpatchpreserved for callers that need more.Backward compatibility
TaskUpdatedMessagesubclassesSystemMessage, so existingisinstance(msg, SystemMessage)andcase SystemMessage()checks still match;subtypeanddataremain populated with the raw payload.SystemMessagesubclasses are covered structurally by theMessageunion rather than listed individually.Tests
Added parser tests for: terminal
completed; the minimal observed shape (nouuid/session_id);running/non-terminal; missingpatch;patchpresent withoutstatus(preserved verbatim); non-dict/Nonepatch(parametrized — never raises); all terminal statusescompleted/failed/stopped/cancelled(parametrized); andSystemMessagebackward-compat.ruff check,ruff format,mypy src/, and the full suite (775 passed, 5 skipped) all green.🤖 Generated with Claude Code