Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion dotnet/src/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ public async Task<string> SendAsync(MessageOptions options, CancellationToken ca
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>A task that resolves with the final assistant message event, or null if none was received.</returns>
/// <exception cref="TimeoutException">Thrown if the timeout is reached before the session becomes idle.</exception>
/// <exception cref="OperationCanceledException">Thrown if the <paramref name="cancellationToken"/> is cancelled.</exception>
/// <exception cref="InvalidOperationException">Thrown if the session has been disposed.</exception>
/// <remarks>
/// <para>
Expand Down Expand Up @@ -201,7 +202,12 @@ void Handler(SessionEvent evt)
cts.CancelAfter(effectiveTimeout);

using var registration = cts.Token.Register(() =>
tcs.TrySetException(new TimeoutException($"SendAndWaitAsync timed out after {effectiveTimeout}")));
{
if (cancellationToken.IsCancellationRequested)
tcs.TrySetCanceled(cancellationToken);
else
tcs.TrySetException(new TimeoutException($"SendAndWaitAsync timed out after {effectiveTimeout}"));
});
return await tcs.Task;
}

Expand Down
24 changes: 24 additions & 0 deletions dotnet/test/SessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,30 @@ public async Task SendAndWait_Throws_On_Timeout()
Assert.Contains("timed out", ex.Message);
}

[Fact]
public async Task SendAndWait_Throws_OperationCanceledException_When_Token_Cancelled()
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
public async Task SendAndWait_Throws_OperationCanceledException_When_Token_Cancelled()
public async Task SendAndWait_Throws_OperationCanceledException_When_Token_Canceled()

Copilot uses AI. Check for mistakes.
{
var session = await Client.CreateSessionAsync();

// Set up wait for tool execution to start BEFORE sending
var toolStartTask = TestHelper.GetNextEventOfTypeAsync<ToolExecutionStartEvent>(session);

Comment on lines +409 to +413
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
using var cts = new CancellationTokenSource();

// Start SendAndWaitAsync - don't await it yet
var sendTask = session.SendAndWaitAsync(
new MessageOptions { Prompt = "run the shell command 'sleep 10' (note this works on both bash and PowerShell)" },
cancellationToken: cts.Token);

// Wait for the tool to begin executing before cancelling
await toolStartTask;

// Cancel the token
cts.Cancel();

await Assert.ThrowsAnyAsync<OperationCanceledException>(() => sendTask);
}

[Fact]
public async Task Should_Create_Session_With_Custom_Config_Dir()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
models:
- claude-sonnet-4.5
conversations:
- messages:
- role: system
content: ${system}
- role: user
content: run the shell command 'sleep 10' (note this works on both bash and PowerShell)
- role: assistant
content: I'll run the sleep command for 10 seconds.
- role: assistant
tool_calls:
- id: toolcall_0
type: function
function:
name: report_intent
arguments: '{"intent":"Running sleep command"}'
- role: assistant
tool_calls:
- id: toolcall_1
type: function
function:
name: ${shell}
arguments: '{"command":"sleep 10","description":"Run sleep 10 command","mode":"sync","initial_wait":15}'
Loading