Skip to content

.NET: Align WorkflowOutputEvent streaming deserialization with SourceId to ExecutorId rename#6896

Open
kshyju wants to merge 5 commits into
mainfrom
kshyju-fix-workflow-output-event-deserializatio
Open

.NET: Align WorkflowOutputEvent streaming deserialization with SourceId to ExecutorId rename#6896
kshyju wants to merge 5 commits into
mainfrom
kshyju-fix-workflow-output-event-deserializatio

Conversation

@kshyju

@kshyju kshyju commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Motivation & Context

PR #3441 renamed WorkflowOutputEvent.SourceId to ExecutorId (keeping SourceId as an [Obsolete] + [JsonIgnore] alias), but the manual streaming deserialization in DeserializeEventByType was not updated. It still calls root.GetProperty("sourceId"), which throws a KeyNotFoundException at runtime because the serialized JSON contains "executorId".

This causes WatchStreamAsync to crash with an unhandled exception whenever the stream includes a WorkflowOutputEvent (e.g., when an executor calls YieldOutputAsync).

Description & Review Guide

  • What are the major changes?
    Updated the WorkflowOutputEvent branch in DeserializeEventByType to read "executorId" from the JSON payload instead of "sourceId". Used variable name outputExecutorId to avoid shadowing the executorId variable declared in the ExecutorInvokedEvent/ExecutorCompletedEvent branches above.

  • What is the impact of these changes?
    Fixes a runtime crash in WatchStreamAsync for any durable workflow that yields output. No behavioral change beyond fixing the deserialization to match the current property name.

  • What do you want reviewers to focus on?
    Confirming there are no other references to the old "sourceId" JSON key in the durable task serialization/deserialization paths.

Related Issue

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change.

kshyju and others added 2 commits July 2, 2026 13:17
The WorkflowOutputEvent.SourceId property was renamed to ExecutorId,
but DeserializeEventByType still looked for "sourceId" in the JSON
payload. This caused a KeyNotFoundException when WatchStreamAsync
encountered a WorkflowOutputEvent during streaming.

Update the manual deserialization to read "executorId" instead.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Covers the serialize-deserialize path through DeserializeEventByType
for WorkflowOutputEvent, which was missing and allowed the sourceId
to executorId rename regression to go undetected.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 2, 2026 21:06
@giles17 giles17 added the .NET Usage: [Issues, PRs], Target: .Net label Jul 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a runtime crash in durable workflow streaming by aligning the manual WorkflowOutputEvent JSON deserialization with the post-#3441 rename from SourceId to ExecutorId.

Changes:

  • Update DeserializeEventByType to read executorId for WorkflowOutputEvent deserialization.
  • Add a unit test that validates WorkflowOutputEvent round-trips through WatchStreamAsync without throwing.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableStreamingWorkflowRun.cs Updates the manual streaming deserialization path for WorkflowOutputEvent to match the new JSON key (executorId).
dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/Workflows/DurableStreamingWorkflowRunTests.cs Adds coverage to ensure WorkflowOutputEvent can be streamed/deserialized correctly via WatchStreamAsync.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 5 | Confidence: 94% | Result: All clear

Reviewed: Correctness, Security Reliability, Test Coverage, Failure Modes, Design Approach


Automated review by kshyju's agents

Use TryGetProperty with sourceId fallback for backward compatibility
with older persisted streams. Add CHANGELOG entry per repo convention.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@giles17 giles17 added the documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs label Jul 2, 2026
@kshyju kshyju requested a review from Copilot July 2, 2026 21:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md Outdated
Add test covering the TryGetProperty fallback path for legacy sourceId
payloads. Add PR link to CHANGELOG entry to match repo convention.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Rename legacyEventJson to LegacyEventJson to satisfy IDE1006 naming
rule requiring constants to use PascalCase.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@kshyju kshyju deployed to integration July 2, 2026 21:45 — with GitHub Actions Active
@kshyju kshyju marked this pull request as ready for review July 2, 2026 21:45

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 5 | Confidence: 94%

✓ Correctness

The fix is correct and well-tested. It replaces the crash-prone GetProperty("sourceId") call with TryGetProperty("executorId") and a fallback to TryGetProperty("sourceId"), matching the current WorkflowOutputEvent class where ExecutorId is the primary property (serialized as executorId) and SourceId is an [Obsolete] + [JsonIgnore] alias. The CHANGELOG entry follows the project convention with a PR link. Both the new-format and legacy-format deserialization paths are covered by tests. No correctness issues found.

✓ Security Reliability

The fix correctly replaces a crash-prone GetProperty("sourceId") call with a robust TryGetProperty fallback chain that handles both the new executorId and legacy sourceId JSON keys. The CHANGELOG is properly formatted with a PR link. Tests cover both the modern and legacy deserialization paths. No security or reliability issues are introduced by this change.

✓ Test Coverage

The PR has good test coverage for the fix. Two new tests exercise both the modern executorId JSON path and the legacy sourceId fallback path. Assertions are meaningful — they verify the deserialized ExecutorId, Data, and Tags properties. The only untested edge case is when neither key is present (defaults to empty string), but this is a minor defensive path for corrupted payloads and not worth blocking on. The pre-existing gap where tags are not deserialized in the manual path is not introduced by this PR.

✓ Failure Modes

The PR correctly fixes a runtime crash in WatchStreamAsync caused by the sourceId→executorId rename. The use of TryGetProperty with fallback is the right approach since the surrounding catch block only handles JsonException, not the KeyNotFoundException that GetProperty would throw for missing keys. Tests cover both the new and legacy deserialization paths. CHANGELOG entry matches project conventions. No new failure modes introduced.

✓ Design Approach

I found one design issue in the new WorkflowOutputEvent fallback: it preserves backward compatibility for sourceId, but it also changes malformed payload handling from "reject as unparsable" to "silently construct an event with an empty executor ID." That weakens the event contract and can hide corrupted stream data. I did not find other design-level issues in the diff.


Automated review by kshyju's agents

? execIdElem.GetString() ?? string.Empty
: root.TryGetProperty("sourceId", out JsonElement srcIdElem)
? srcIdElem.GetString() ?? string.Empty
: string.Empty;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fallback now silently accepts a malformed payload like {"data":"x"} and produces a WorkflowOutputEvent with an empty ExecutorId. That violates the documented invariant that ExecutorId is "The unique identifier of the executor that yielded this output." The surrounding caller already treats JsonException as an unparsable event (DurableStreamingWorkflowRun.cs:395-397), so the backward-compat path should still fail closed when neither key is present instead of manufacturing anonymous output event.

Suggested change
: string.Empty;
string outputExecutorId = root.TryGetProperty("executorId", out JsonElement execIdElem)
? execIdElem.GetString() ?? string.Empty
: root.TryGetProperty("sourceId", out JsonElement srcIdElem)
? srcIdElem.GetString() ?? string.Empty
: throw new JsonException("WorkflowOutputEvent is missing required executorId/sourceId.");

@kshyju kshyju requested a review from a team July 2, 2026 22:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs .NET Usage: [Issues, PRs], Target: .Net

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants