From e2c763df88502966eb293d941e6fb6ada3a33411 Mon Sep 17 00:00:00 2001 From: Jon Galloway Date: Mon, 16 Mar 2026 18:49:03 -0700 Subject: [PATCH] Update .NET Copilot SDK recipes for explicit permission (fix for SDK v0.1.28) (#1033) * Update .NET Copilot SDK recipes for explicit permission handling (fix breaking change in SDK v0.1.28) * Update .NET Copilot SDK recipes for explicit permission handling --- .../dotnet/accessibility-report.md | 2 ++ cookbook/copilot-sdk/dotnet/error-handling.md | 23 +++++++++++++++---- .../dotnet/managing-local-files.md | 8 ++++--- .../copilot-sdk/dotnet/multiple-sessions.md | 23 +++++++++++++++---- .../copilot-sdk/dotnet/persisting-sessions.md | 3 ++- .../copilot-sdk/dotnet/pr-visualization.md | 1 + cookbook/copilot-sdk/dotnet/ralph-loop.md | 9 +++++--- .../dotnet/recipe/accessibility-report.cs | 1 + .../dotnet/recipe/error-handling.cs | 3 ++- .../dotnet/recipe/managing-local-files.cs | 6 ++--- .../dotnet/recipe/multiple-sessions.cs | 18 ++++++++++++--- .../dotnet/recipe/persisting-sessions.cs | 3 ++- .../dotnet/recipe/pr-visualization.cs | 1 + .../copilot-sdk/dotnet/recipe/ralph-loop.cs | 3 +-- 14 files changed, 78 insertions(+), 26 deletions(-) diff --git a/cookbook/copilot-sdk/dotnet/accessibility-report.md b/cookbook/copilot-sdk/dotnet/accessibility-report.md index 6c2f1d8d..cc2d3063 100644 --- a/cookbook/copilot-sdk/dotnet/accessibility-report.md +++ b/cookbook/copilot-sdk/dotnet/accessibility-report.md @@ -64,6 +64,7 @@ await using var session = await client.CreateSessionAsync(new SessionConfig { Model = "claude-opus-4.6", Streaming = true, + OnPermissionRequest = PermissionHandler.ApproveAll, McpServers = new Dictionary() { ["playwright"] = @@ -207,6 +208,7 @@ if (generateTests == "y" || generateTests == "yes") The recipe configures a local MCP server that runs alongside the session: ```csharp +OnPermissionRequest = PermissionHandler.ApproveAll, McpServers = new Dictionary() { ["playwright"] = new McpLocalServerConfig diff --git a/cookbook/copilot-sdk/dotnet/error-handling.md b/cookbook/copilot-sdk/dotnet/error-handling.md index 02a685ab..68f45e50 100644 --- a/cookbook/copilot-sdk/dotnet/error-handling.md +++ b/cookbook/copilot-sdk/dotnet/error-handling.md @@ -24,7 +24,8 @@ try await client.StartAsync(); var session = await client.CreateSessionAsync(new SessionConfig { - Model = "gpt-5" + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll }); var done = new TaskCompletionSource(); @@ -76,7 +77,11 @@ catch (Exception ex) ## Timeout handling ```csharp -var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); +var session = await client.CreateSessionAsync(new SessionConfig +{ + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll +}); try { @@ -106,7 +111,11 @@ catch (OperationCanceledException) ## Aborting a request ```csharp -var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); +var session = await client.CreateSessionAsync(new SessionConfig +{ + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll +}); // Start a request await session.SendAsync(new MessageOptions { Prompt = "Write a very long story..." }); @@ -141,7 +150,11 @@ Console.CancelKeyPress += async (sender, e) => await using var client = new CopilotClient(); await client.StartAsync(); -var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); +var session = await client.CreateSessionAsync(new SessionConfig +{ + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll +}); // ... do work ... @@ -150,6 +163,8 @@ var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5 ## Best practices +Starting with Copilot SDK v0.1.28, permission handling is opt-in. If a session may need tool, file, or system access, set `OnPermissionRequest` explicitly when creating it. + 1. **Always clean up**: Use try-finally or `await using` to ensure `StopAsync()` is called 2. **Handle connection errors**: The CLI might not be installed or running 3. **Set appropriate timeouts**: Use `CancellationToken` for long-running requests diff --git a/cookbook/copilot-sdk/dotnet/managing-local-files.md b/cookbook/copilot-sdk/dotnet/managing-local-files.md index 105758b6..efe07c7d 100644 --- a/cookbook/copilot-sdk/dotnet/managing-local-files.md +++ b/cookbook/copilot-sdk/dotnet/managing-local-files.md @@ -6,6 +6,7 @@ Use Copilot to intelligently organize files in a folder based on their metadata. > > ```bash > dotnet run recipe/managing-local-files.cs +> dotnet run recipe/managing-local-files.cs -- /path/to/folder > ``` ## Example scenario @@ -24,7 +25,8 @@ await client.StartAsync(); // Define tools for file operations var session = await client.CreateSessionAsync(new SessionConfig { - Model = "gpt-5" + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll }); // Wait for completion @@ -49,8 +51,8 @@ session.On(evt => } }); -// Ask Copilot to organize files -var targetFolder = @"C:\Users\Me\Downloads"; +// Use an explicit folder or default to the current directory +var targetFolder = args.FirstOrDefault() ?? Environment.CurrentDirectory; await session.SendAsync(new MessageOptions { diff --git a/cookbook/copilot-sdk/dotnet/multiple-sessions.md b/cookbook/copilot-sdk/dotnet/multiple-sessions.md index 9ce05e80..630301b7 100644 --- a/cookbook/copilot-sdk/dotnet/multiple-sessions.md +++ b/cookbook/copilot-sdk/dotnet/multiple-sessions.md @@ -12,7 +12,7 @@ Manage multiple independent conversations simultaneously. You need to run multiple conversations in parallel, each with its own context and history. -## C# +## C # ```csharp using GitHub.Copilot.SDK; @@ -21,9 +21,21 @@ await using var client = new CopilotClient(); await client.StartAsync(); // Create multiple independent sessions -var session1 = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); -var session2 = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); -var session3 = await client.CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-4.5" }); +var session1 = await client.CreateSessionAsync(new SessionConfig +{ + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll +}); +var session2 = await client.CreateSessionAsync(new SessionConfig +{ + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll +}); +var session3 = await client.CreateSessionAsync(new SessionConfig +{ + Model = "claude-sonnet-4.5", + OnPermissionRequest = PermissionHandler.ApproveAll +}); // Each session maintains its own conversation history await session1.SendAsync(new MessageOptions { Prompt = "You are helping with a Python project" }); @@ -49,7 +61,8 @@ Use custom IDs for easier tracking: var session = await client.CreateSessionAsync(new SessionConfig { SessionId = "user-123-chat", - Model = "gpt-5" + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll }); Console.WriteLine(session.SessionId); // "user-123-chat" diff --git a/cookbook/copilot-sdk/dotnet/persisting-sessions.md b/cookbook/copilot-sdk/dotnet/persisting-sessions.md index f6a3aa13..01883d2e 100644 --- a/cookbook/copilot-sdk/dotnet/persisting-sessions.md +++ b/cookbook/copilot-sdk/dotnet/persisting-sessions.md @@ -25,7 +25,8 @@ await client.StartAsync(); var session = await client.CreateSessionAsync(new SessionConfig { SessionId = "user-123-conversation", - Model = "gpt-5" + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll }); await session.SendAsync(new MessageOptions { Prompt = "Let's discuss TypeScript generics" }); diff --git a/cookbook/copilot-sdk/dotnet/pr-visualization.md b/cookbook/copilot-sdk/dotnet/pr-visualization.md index f427ebe1..cdd6d808 100644 --- a/cookbook/copilot-sdk/dotnet/pr-visualization.md +++ b/cookbook/copilot-sdk/dotnet/pr-visualization.md @@ -165,6 +165,7 @@ await client.StartAsync(); var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll, SystemMessage = new SystemMessageConfig { Content = $""" diff --git a/cookbook/copilot-sdk/dotnet/ralph-loop.md b/cookbook/copilot-sdk/dotnet/ralph-loop.md index 8ff85246..2b07b465 100644 --- a/cookbook/copilot-sdk/dotnet/ralph-loop.md +++ b/cookbook/copilot-sdk/dotnet/ralph-loop.md @@ -58,7 +58,11 @@ try // Fresh session each iteration — context isolation is the point var session = await client.CreateSessionAsync( - new SessionConfig { Model = "gpt-5.1-codex-mini" }); + new SessionConfig + { + Model = "gpt-5.1-codex-mini", + OnPermissionRequest = PermissionHandler.ApproveAll + }); try { var done = new TaskCompletionSource(); @@ -125,8 +129,7 @@ try // Pin the agent to the project directory WorkingDirectory = Environment.CurrentDirectory, // Auto-approve tool calls for unattended operation - OnPermissionRequest = (_, _) => Task.FromResult( - new PermissionRequestResult { Kind = "approved" }), + OnPermissionRequest = PermissionHandler.ApproveAll, }); try { diff --git a/cookbook/copilot-sdk/dotnet/recipe/accessibility-report.cs b/cookbook/copilot-sdk/dotnet/recipe/accessibility-report.cs index 3fe4e387..c5b67bcb 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/accessibility-report.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/accessibility-report.cs @@ -32,6 +32,7 @@ await using var session = await client.CreateSessionAsync(new SessionConfig { Model = "claude-opus-4.6", Streaming = true, + OnPermissionRequest = PermissionHandler.ApproveAll, McpServers = new Dictionary() { ["playwright"] = diff --git a/cookbook/copilot-sdk/dotnet/recipe/error-handling.cs b/cookbook/copilot-sdk/dotnet/recipe/error-handling.cs index 3f0e9bb0..5932cc88 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/error-handling.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/error-handling.cs @@ -10,7 +10,8 @@ try await client.StartAsync(); var session = await client.CreateSessionAsync(new SessionConfig { - Model = "gpt-5" + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll }); var done = new TaskCompletionSource(); diff --git a/cookbook/copilot-sdk/dotnet/recipe/managing-local-files.cs b/cookbook/copilot-sdk/dotnet/recipe/managing-local-files.cs index 859e0d5d..72ff1cd6 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/managing-local-files.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/managing-local-files.cs @@ -10,7 +10,8 @@ await client.StartAsync(); // Define tools for file operations var session = await client.CreateSessionAsync(new SessionConfig { - Model = "gpt-5" + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll }); // Wait for completion @@ -36,8 +37,7 @@ session.On(evt => }); // Ask Copilot to organize files -// Change this to your target folder -var targetFolder = @"C:\Users\Me\Downloads"; +var targetFolder = args.FirstOrDefault() ?? Environment.CurrentDirectory; await session.SendAsync(new MessageOptions { diff --git a/cookbook/copilot-sdk/dotnet/recipe/multiple-sessions.cs b/cookbook/copilot-sdk/dotnet/recipe/multiple-sessions.cs index 166606aa..c65557dc 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/multiple-sessions.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/multiple-sessions.cs @@ -7,9 +7,21 @@ await using var client = new CopilotClient(); await client.StartAsync(); // Create multiple independent sessions -var session1 = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); -var session2 = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" }); -var session3 = await client.CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-4.5" }); +var session1 = await client.CreateSessionAsync(new SessionConfig +{ + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll +}); +var session2 = await client.CreateSessionAsync(new SessionConfig +{ + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll +}); +var session3 = await client.CreateSessionAsync(new SessionConfig +{ + Model = "claude-sonnet-4.5", + OnPermissionRequest = PermissionHandler.ApproveAll +}); Console.WriteLine("Created 3 independent sessions"); diff --git a/cookbook/copilot-sdk/dotnet/recipe/persisting-sessions.cs b/cookbook/copilot-sdk/dotnet/recipe/persisting-sessions.cs index 72d3659e..e7bb64dc 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/persisting-sessions.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/persisting-sessions.cs @@ -10,7 +10,8 @@ await client.StartAsync(); var session = await client.CreateSessionAsync(new SessionConfig { SessionId = "user-123-conversation", - Model = "gpt-5" + Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll }); await session.SendAsync(new MessageOptions { Prompt = "Let's discuss TypeScript generics" }); diff --git a/cookbook/copilot-sdk/dotnet/recipe/pr-visualization.cs b/cookbook/copilot-sdk/dotnet/recipe/pr-visualization.cs index b635f5ca..40b7548e 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/pr-visualization.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/pr-visualization.cs @@ -132,6 +132,7 @@ await client.StartAsync(); var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5", + OnPermissionRequest = PermissionHandler.ApproveAll, SystemMessage = new SystemMessageConfig { Content = $""" diff --git a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs index 9f153324..ed251e8d 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs @@ -48,8 +48,7 @@ try // Pin the agent to the project directory WorkingDirectory = Environment.CurrentDirectory, // Auto-approve tool calls for unattended operation - OnPermissionRequest = (_, _) => Task.FromResult( - new PermissionRequestResult { Kind = "approved" }), + OnPermissionRequest = PermissionHandler.ApproveAll, }); try