Skip to content

[.NET] Always return JSON from workflow status and respond endpoints - #60

Open
Shyju Krishnankutty (kshyju) wants to merge 2 commits into
mainfrom
kshyju/gh-56-workflow-json-errors
Open

[.NET] Always return JSON from workflow status and respond endpoints#60
Shyju Krishnankutty (kshyju) wants to merge 2 commits into
mainfrom
kshyju/gh-56-workflow-json-errors

Conversation

@kshyju

Copy link
Copy Markdown
Contributor

Fixes #56 (the remaining half — #52 already fixed the 202 run response).

Problem

status/{runId} and respond/{runId} always write JSON on success, but their error paths fell through to AcceptsJson(req), which needs an explicit Accept: application/json. Callers sending no Accept header — or */*, curl's default — got JSON on success and text/plain on failure from the same endpoint.

Separately, the malformed-body guard never fired: ReadFromJsonAsync reads Task.Result in a ContinueWith, so bad JSON arrives as AggregateException wrapping JsonException. The request failed as an unhandled 500 instead of 400.

Change

  • All 8 error paths on both endpoints now force JSON. These endpoints have no text representation, so they don't negotiate — the run endpoint still does, since it has one.
  • Widened the catch, restoring the intended 400 Bad Request.

Python parity

Python already behaves this way. _build_error_response in _app.py hardcodes mimetype=application/json with no Accept inspection, and both workflow/{name}/status/{id} and workflow/{name}/respond/{id}/{requestId} return JSON on success and error alike. This brings .NET in line.

Tests

40 cases (10 scenarios x 4 Accept values). First commit is intentionally red (15/25), second turns it green.

The workflow status and respond endpoints always write JSON on success but
route every failure through CreateErrorResponseAsync without passing
acceptsJson, so they fall back to AcceptsJson(req). That helper requires an
explicit Accept header naming application/json, which means a client that
sends no Accept header gets JSON on 200/202 and text/plain on 400/404.

These tests assert JSON on both the success and error paths for all eight
error call sites, across Accept: absent, text/plain, application/json, and
*/*. They fail today for every value except application/json.

RespondToWorkflowAsync_MalformedBody also fails for application/json. The
catch (JsonException) around ReadFromJsonAsync never fires: the worker's
HttpRequestDataExtensions.ReadFromJsonAsync ends in
ContinueWith(t => TryCast(t.Result)), and t.Result on a faulted task throws
AggregateException. Malformed bodies surface as an unhandled 500 instead of
the intended 400.

Relates to #56

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 062445ed-7aad-4643-87b4-d1bc7313364a
Both endpoints write JSON unconditionally on success but routed every failure
through CreateErrorResponseAsync without an acceptsJson argument. That falls
back to AcceptsJson(req), which requires an explicit Accept header naming
application/json, so a client sending no Accept header received JSON on
200/202 and text/plain on 400/404. The response shape flipped within a single
endpoint, precisely when the caller was trying to determine what went wrong.

Neither endpoint has a plain-text representation, so both now force JSON at
all eight error call sites rather than negotiating. This differs from the run
endpoint, which keeps ShouldReturnWorkflowJson because it does have a
meaningful text form.

Also fixes malformed request bodies on the respond endpoint. The worker's
HttpRequestDataExtensions.ReadFromJsonAsync ends in
ContinueWith(t => TryCast(t.Result)), and Task.Result on a faulted task
throws AggregateException, so catch (JsonException) never fired and a bad
body surfaced as an unhandled 500 instead of the intended 400.

Fixes #56

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 062445ed-7aad-4643-87b4-d1bc7313364a
Copilot AI review requested due to automatic review settings August 3, 2026 18:14

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

This PR fixes the .NET Azure Functions built-in workflow status and respond HTTP endpoints so they consistently return JSON on both success and failure, independent of the request’s Accept header, and restores the intended 400 Bad Request behavior for malformed JSON request bodies.

Changes:

  • Force JSON error responses for status/{runId} and respond/{runId} by passing acceptsJson: true through all error paths.
  • Broaden JSON-deserialization error handling to correctly map malformed bodies to 400 Bad Request (including AggregateException-wrapped JsonException cases).
  • Add unit tests covering success and all error scenarios across multiple Accept header values, and document the breaking behavior in the hosting CHANGELOG.

Reviewed changes

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

File Description
dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs Forces JSON on all error paths for status/respond endpoints and widens malformed-body exception handling to return 400.
dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/BuiltInFunctionsWorkflowJsonEndpointTests.cs Adds coverage to ensure status/respond endpoints return JSON for both success and error across Accept variants.
dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/CHANGELOG.md Adds an Unreleased entry documenting the breaking behavior change and malformed-body fix.

@kshyju
Shyju Krishnankutty (kshyju) marked this pull request as ready for review August 3, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.NET Functions host returns unparsable text/plain from workflow HTTP endpoints

2 participants