Skip to content

Fix: Propagate native errors from ExecuteCommandWithCallbackAsync in streaming paths#466

Open
Copilot wants to merge 5 commits intomainfrom
copilot/check-execute-command-response
Open

Fix: Propagate native errors from ExecuteCommandWithCallbackAsync in streaming paths#466
Copilot wants to merge 5 commits intomainfrom
copilot/check-execute-command-response

Conversation

Copy link
Contributor

Copilot AI commented Feb 27, 2026

  • Fix ChatStreamingImplAsync in ChatClient.cs to capture and check Response.Error from ExecuteCommandWithCallbackAsync
  • Fix TranscribeAudioStreamingImplAsync in AudioClient.cs to capture and check Response.Error from ExecuteCommandWithCallbackAsync
  • Add !failed guard before response.Error check in both ChatClient.cs and AudioClient.cs for clarity and consistency
  • Add internal constructor to OpenAIChatClient for dependency injection in tests
  • Add internal constructor to OpenAIAudioClient for dependency injection in tests
  • Update Utils.CreateCoreInteropWithIntercept to also mock ExecuteCommandWithCallbackAsync
  • Add unit tests for error propagation in ChatClient streaming (ChatClientStreamingErrorTests.cs)
  • Add unit tests for error propagation in AudioClient streaming (AudioClientStreamingErrorTests.cs)
  • Fix build error: replace unsupported ThrowsAsync<T>() calls with try-catch + IsNotNull assertion
Original prompt

This section details on the original issue you should resolve

<issue_title>ExecuteCommandWithCallbackAsync response not checked</issue_title>
<issue_description>ExecuteCommandWithCallbackAsync returns a Response whose .Error field may contain an error from the native layer (e.g. missing audio file, invalid model, etc.). Both AudioClient and ChatClient streaming paths are ignoring this return value, so when the native side reported an error without invoking any callbacks the channel was completed successfully and the caller observed an empty stream instead of an exception.

Capture the Response and, when it carries an error, complete the channel with a FoundryLocalException so the error propagates to the consumer's await foreach loop.</issue_description>

<agent_instructions>Add necessary unit tests.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

@vercel
Copy link

vercel bot commented Feb 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
foundry-local Ready Ready Preview, Comment Feb 27, 2026 7:52am

Request Review

…ient and AudioClient streaming paths"

- Capture Response from ExecuteCommandWithCallbackAsync and propagate Response.Error as FoundryLocalException
- Add internal constructors to OpenAIChatClient and OpenAIAudioClient for dependency injection in tests
- Extend Utils.CreateCoreInteropWithIntercept to also mock ExecuteCommandWithCallbackAsync
- Add ChatClientStreamingErrorTests and AudioClientStreamingErrorTests unit test classes

Co-authored-by: baijumeswani <12852605+baijumeswani@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix ExecuteCommandWithCallbackAsync error handling Fix: Propagate native errors from ExecuteCommandWithCallbackAsync in streaming paths Feb 27, 2026
@baijumeswani
Copy link
Contributor

@copilot can you please address the pipeline failures?

     1>D:\a\Foundry-Local\Foundry-Local\sdk_v2\cs\test\FoundryLocal.Tests\AudioClientTests.cs(25,15): warning CS8602: Dereference of a possibly null reference. [D:\a\Foundry-Local\Foundry-Local\sdk_v2\cs\test\FoundryLocal.Tests\Microsoft.AI.Foundry.Local.Tests.csproj]
     1>D:\a\Foundry-Local\Foundry-Local\sdk_v2\cs\test\FoundryLocal.Tests\ChatClientStreamingErrorTests.cs(53,12): error CS1501: No overload for method 'ThrowsAsync' takes 0 arguments [D:\a\Foundry-Local\Foundry-Local\sdk_v2\cs\test\FoundryLocal.Tests\Microsoft.AI.Foundry.Local.Tests.csproj]
     1>D:\a\Foundry-Local\Foundry-Local\sdk_v2\cs\test\FoundryLocal.Tests\AudioClientStreamingErrorTests.cs(45,12): error CS1501: No overload for method 'ThrowsAsync' takes 0 arguments [D:\a\Foundry-Local\Foundry-Local\sdk_v2\cs\test\FoundryLocal.Tests\Microsoft.AI.Foundry.Local.Tests.csproj]
     1>D:\a\Foundry-Local\Foundry-Local\sdk_v2\cs\test\FoundryLocal.Tests\Utils.cs(73,31): warning CA1727: Use PascalCase for named placeholders in the logging message template (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1727) [D:\a\Foundry-Local\Foundry-Local\sdk_v2\cs\test\FoundryLocal.Tests\Microsoft.AI.Foundry.Local.Tests.csproj]
         CompilerServer: server - server processed compilation - Microsoft.AI.Foundry.Local.Tests (net9.0)
     1>Done Building Project "D:\a\Foundry-Local\Foundry-Local\sdk_v2\cs\test\FoundryLocal.Tests\Microsoft.AI.Foundry.Local.Tests.csproj" (default targets) -- FAILED.
     1>Done Building Project "D:\a\Foundry-Local\Foundry-Local\sdk_v2\cs\test\FoundryLocal.Tests\Microsoft.AI.Foundry.Local.Tests.csproj" (VSTest target(s)) -- FAILED.

TUnit 0.63.3 doesn't support the zero-argument ThrowsAsync<T>() generic overload
that was used in ChatClientStreamingErrorTests and AudioClientStreamingErrorTests.
Replace with the explicit try-catch + IsNotNull assertion pattern used elsewhere.

Co-authored-by: baijumeswani <12852605+baijumeswani@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 27, 2026

@copilot can you please address the pipeline failures?

...

Fixed in 83c579d. TUnit 0.63.3 doesn't have a zero-argument ThrowsAsync<T>() overload, so I replaced those calls with the explicit try-catch + IsNotNull assertion pattern used in the adjacent tests.

Copy link
Contributor

Copilot AI left a comment

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 a bug where streaming methods in ChatClient and AudioClient were not checking the Response.Error field returned by ExecuteCommandWithCallbackAsync. When the native layer reported an error without invoking any callbacks, the streaming methods would silently complete with an empty stream instead of propagating the error to the caller.

Changes:

  • Fixed error propagation in streaming methods by capturing and checking Response.Error from ExecuteCommandWithCallbackAsync
  • Added internal constructors with dependency injection support to enable unit testing
  • Added comprehensive unit tests for error propagation scenarios in streaming methods

Reviewed changes

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

Show a summary per file
File Description
sdk_v2/cs/src/OpenAI/ChatClient.cs Added DI constructor; captured response from ExecuteCommandWithCallbackAsync; added error check to complete channel with exception when response.Error is set
sdk_v2/cs/src/OpenAI/AudioClient.cs Added DI constructor; captured response from ExecuteCommandWithCallbackAsync; added error check to complete channel with exception when response.Error is set
sdk_v2/cs/test/FoundryLocal.Tests/Utils.cs Extended CreateCoreInteropWithIntercept to mock ExecuteCommandWithCallbackAsync for pass-through behavior
sdk_v2/cs/test/FoundryLocal.Tests/ChatClientStreamingErrorTests.cs New unit tests verifying ChatClient streaming properly propagates native errors via exceptions
sdk_v2/cs/test/FoundryLocal.Tests/AudioClientStreamingErrorTests.cs New unit tests verifying AudioClient streaming properly propagates native errors via exceptions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 209 to 214
if (response.Error != null)
{
channel.Writer.TryComplete(
new FoundryLocalException($"Error from chat_completions command: {response.Error}", _logger));
return;
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

Consider checking the failed flag before attempting to complete the channel with response.Error. If a callback exception has already completed the channel (failed=true), attempting to complete it again with response.Error could be confusing. While TryComplete makes it safe, checking if (!failed && response.Error != null) would make the intent clearer and avoid redundant error handling.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot please address this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 6db84d7. Added !failed && guard and failed = true after completing the channel with the error — now matches the pattern already used in AudioClient.cs.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Align ChatClient.cs with AudioClient.cs: only complete the channel
with response.Error if a callback exception has not already closed it,
making the intent clearer and consistent across both streaming clients.

Co-authored-by: baijumeswani <12852605+baijumeswani@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 27, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • 1javsblobprodcus364.vsblob.vsassets.io
    • Triggering command: /usr/bin/dotnet dotnet build src/Microsoft.AI.Foundry.Local.csproj (dns block)
  • 51yvsblobprodcus36.vsblob.vsassets.io
    • Triggering command: /usr/bin/dotnet dotnet build src/Microsoft.AI.Foundry.Local.csproj (dns block)
  • rcxvsblobprodcus328.vsblob.vsassets.io
    • Triggering command: /usr/bin/dotnet dotnet build src/Microsoft.AI.Foundry.Local.csproj (dns block)
  • se1vsblobprodcus349.vsblob.vsassets.io
    • Triggering command: /usr/bin/dotnet dotnet build src/Microsoft.AI.Foundry.Local.csproj (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

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.

ExecuteCommandWithCallbackAsync response not checked

3 participants