[dotnet] Fix SendAndWaitAsync to throw OperationCanceledException on external cancellation#543
Conversation
… cancelled Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
…elling Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
Cross-SDK Consistency ReviewThis PR fixes a .NET-specific bug in Cross-SDK ComparisonAfter reviewing the equivalent ✅ Go SDK - Already handles this correctly
.NET SDK - Now fixed with this PR ✅
RecommendationNo action required for this PR - the fix is correct and maintains the existing .NET API surface. The .NET SDK's ability to accept an explicit The other SDKs use different patterns appropriate to their ecosystems:
This PR maintains cross-SDK consistency by ensuring .NET behaves correctly within its own idiomatic patterns. ✅
|
There was a problem hiding this comment.
Pull request overview
Fixes .NET SDK CopilotSession.SendAndWaitAsync cancellation semantics so that externally-canceled CancellationTokens surface as cancellation (instead of being reported as a timeout), aligning the API behavior with .NET expectations and improving diagnosability for callers.
Changes:
- Update
SendAndWaitAsyncto complete the wait task as canceled when the caller’sCancellationTokenis canceled; otherwise keep throwingTimeoutExceptionon timeout. - Update XML docs to include
OperationCanceledException. - Add a new E2E test + snapshot covering external cancellation during an in-flight
SendAndWaitAsync.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
dotnet/src/Session.cs |
Adjusts SendAndWaitAsync completion to distinguish external cancellation vs timeout and documents cancellation behavior. |
dotnet/test/SessionTests.cs |
Adds an E2E regression test validating cancellation behavior. |
test/snapshots/session/sendandwait_throws_operationcanceledexception_when_token_cancelled.yaml |
Adds the replay snapshot for the new E2E test scenario. |
| var session = await Client.CreateSessionAsync(); | ||
|
|
||
| // Set up wait for tool execution to start BEFORE sending | ||
| var toolStartTask = TestHelper.GetNextEventOfTypeAsync<ToolExecutionStartEvent>(session); | ||
|
|
There was a problem hiding this comment.
This test creates a session without an OnPermissionRequest handler but then waits for ToolExecutionStartEvent from a shell command. In this repo, tool operations are expected to be denied-by-default when no permission handler is provided (see PermissionTests.Should_Deny_Tool_Operations_By_Default_When_No_Handler_Is_Provided), which can prevent tool.execution_start from ever arriving or can cause the turn to complete quickly with “Permission denied”, making this test flaky/slow (60s timeout in GetNextEventOfTypeAsync). Consider creating the session with new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll } (or a handler that approves shell), and optionally pass a shorter timeout to GetNextEventOfTypeAsync to fail fast.
| } | ||
|
|
||
| [Fact] | ||
| public async Task SendAndWait_Throws_OperationCanceledException_When_Token_Cancelled() |
There was a problem hiding this comment.
Method name uses Token_Cancelled (double-l). In .NET APIs and the rest of the framework, the standard spelling is Canceled (single-l), e.g. OperationCanceledException / CancellationToken. Consider renaming the test (and corresponding snapshot filename) to ..._When_Token_Canceled for consistency/searchability.
| public async Task SendAndWait_Throws_OperationCanceledException_When_Token_Cancelled() | |
| public async Task SendAndWait_Throws_OperationCanceledException_When_Token_Canceled() |
SendAndWaitAsyncalways throwsTimeoutExceptionwhen the linked CTS fires, even if the user's cancellation token was cancelled externallySession.cs: check if originalcancellationTokenis cancelled; if so callTrySetCanceled, else throwTimeoutExceptionOperationCanceledExceptionSendAndWait_Throws_OperationCanceledException_When_Token_Cancelledthat properly waits forToolExecutionStartEventbefore cancellingOriginal prompt
🔒 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.