Date: Mon, 24 Aug 2026 12:49:17 -0700
Subject: [PATCH 03/11] Adapt Go and Java managed settings types
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e2ffce58-35b6-4bb8-a5f3-0bb4e5b5ada4
---
go/client_test.go | 20 ++++++++++++++++
.../e2e/rpc_tasks_and_handlers_e2e_test.go | 1 +
go/types.go | 9 ++++++--
.../rpc/DisableBypassPermissionsModes.java | 23 +++++++++++++++++++
.../rpc/ManagedSettingsPermissions.java | 7 +++---
.../github/copilot/ManagedSettingsTest.java | 12 ++++++++--
.../rpc/GeneratedRpcRecordsCoverageTest.java | 3 ++-
7 files changed, 66 insertions(+), 9 deletions(-)
create mode 100644 java/sdk/src/main/java/com/github/copilot/rpc/DisableBypassPermissionsModes.java
diff --git a/go/client_test.go b/go/client_test.go
index d0139eb11a..2332a77011 100644
--- a/go/client_test.go
+++ b/go/client_test.go
@@ -3834,6 +3834,26 @@ func TestSessionRequests_ManagedSettings(t *testing.T) {
}
})
+ t.Run("accepts future bypass-permissions modes", func(t *testing.T) {
+ req := createSessionRequest{ManagedSettings: &ManagedSettings{
+ Permissions: &ManagedSettingsPermissions{
+ DisableBypassPermissionsMode: DisableBypassPermissionsMode("future-fail-closed-mode"),
+ },
+ }}
+ data, err := json.Marshal(req)
+ if err != nil {
+ t.Fatalf("Failed to marshal: %v", err)
+ }
+ var m map[string]any
+ if err := json.Unmarshal(data, &m); err != nil {
+ t.Fatalf("Failed to unmarshal: %v", err)
+ }
+ perms := m["managedSettings"].(map[string]any)["permissions"].(map[string]any)
+ if perms["disableBypassPermissionsMode"] != "future-fail-closed-mode" {
+ t.Errorf("Expected future mode preserved, got %v", perms["disableBypassPermissionsMode"])
+ }
+ })
+
t.Run("omits managedSettings when nil", func(t *testing.T) {
req := createSessionRequest{}
data, _ := json.Marshal(req)
diff --git a/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go b/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go
index 0267f8d042..648855e5a3 100644
--- a/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go
+++ b/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go
@@ -127,6 +127,7 @@ func TestRPCTasksAndHandlersE2E(t *testing.T) {
})
t.Run("should report implemented error for invalid task agent model", func(t *testing.T) {
+ ctx.ConfigureForTest(t)
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
diff --git a/go/types.go b/go/types.go
index 60781d1da8..c8cf72197b 100644
--- a/go/types.go
+++ b/go/types.go
@@ -1569,11 +1569,16 @@ type ManagedSettings struct {
}
// DisableBypassPermissionsMode is the managed bypass-permissions policy.
-type DisableBypassPermissionsMode = rpc.DisableBypassPermissionsMode
+//
+// The runtime may introduce additional fail-closed modes. Values are serialized
+// as strings so callers can use newer modes without waiting for an SDK release.
+type DisableBypassPermissionsMode string
const (
// DisableBypassPermissionsModeDisable turns off bypass-permissions mode.
- DisableBypassPermissionsModeDisable = rpc.DisableBypassPermissionsModeDisable
+ DisableBypassPermissionsModeDisable DisableBypassPermissionsMode = "disable"
+ // DisableBypassPermissionsModeAllowAutoOnly permits only automatic bypass.
+ DisableBypassPermissionsModeAllowAutoOnly DisableBypassPermissionsMode = "allow-auto-only"
)
// ManagedSettingsPermissions is the permissions-only managed policy injected
diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/DisableBypassPermissionsModes.java b/java/sdk/src/main/java/com/github/copilot/rpc/DisableBypassPermissionsModes.java
new file mode 100644
index 0000000000..cf98f0526d
--- /dev/null
+++ b/java/sdk/src/main/java/com/github/copilot/rpc/DisableBypassPermissionsModes.java
@@ -0,0 +1,23 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+package com.github.copilot.rpc;
+
+/**
+ * Known values for the managed bypass-permissions policy.
+ *
+ *
+ * The wire contract is an open string so callers can pass newer fail-closed
+ * modes directly to
+ * {@link ManagedSettingsPermissions#setDisableBypassPermissionsMode(String)}.
+ */
+public final class DisableBypassPermissionsModes {
+ /** Turns off bypass-permissions mode. */
+ public static final String DISABLE = "disable";
+
+ /** Permits bypass only for automatic operations. */
+ public static final String ALLOW_AUTO_ONLY = "allow-auto-only";
+
+ private DisableBypassPermissionsModes() {
+ }
+}
diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java
index 0923cea54a..f857391dbc 100644
--- a/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java
+++ b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java
@@ -5,7 +5,6 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.github.copilot.generated.rpc.DisableBypassPermissionsMode;
import java.util.ArrayList;
import java.util.List;
@@ -15,7 +14,7 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public final class ManagedSettingsPermissions {
@JsonProperty("disableBypassPermissionsMode")
- private DisableBypassPermissionsMode disableBypassPermissionsMode;
+ private String disableBypassPermissionsMode;
@JsonProperty("deny")
private List deny;
@@ -27,7 +26,7 @@ public final class ManagedSettingsPermissions {
private List allow;
/** @return the bypass-permissions policy, or {@code null} when unset */
- public DisableBypassPermissionsMode getDisableBypassPermissionsMode() {
+ public String getDisableBypassPermissionsMode() {
return disableBypassPermissionsMode;
}
@@ -38,7 +37,7 @@ public DisableBypassPermissionsMode getDisableBypassPermissionsMode() {
* bypass-permissions policy
* @return this policy
*/
- public ManagedSettingsPermissions setDisableBypassPermissionsMode(DisableBypassPermissionsMode value) {
+ public ManagedSettingsPermissions setDisableBypassPermissionsMode(String value) {
this.disableBypassPermissionsMode = value;
return this;
}
diff --git a/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java b/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java
index dbd19f3c97..d6341b26c5 100644
--- a/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java
@@ -8,7 +8,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.github.copilot.generated.rpc.DisableBypassPermissionsMode;
+import com.github.copilot.rpc.DisableBypassPermissionsModes;
import com.github.copilot.rpc.ManagedSettings;
import com.github.copilot.rpc.ManagedSettingsPermissions;
import com.github.copilot.rpc.PermissionRequestResult;
@@ -23,7 +23,7 @@ class ManagedSettingsTest {
@Test
void forwardsManagedSettingsOnCreateAndResume() throws Exception {
var permissions = new ManagedSettingsPermissions()
- .setDisableBypassPermissionsMode(DisableBypassPermissionsMode.DISABLE).setDeny(List.of("Shell(rm *)"))
+ .setDisableBypassPermissionsMode(DisableBypassPermissionsModes.DISABLE).setDeny(List.of("Shell(rm *)"))
.setAsk(List.of("Domain(publish.example)")).setAllow(List.of("Read(**)"));
var managedSettings = new ManagedSettings().setPermissions(permissions);
@@ -41,6 +41,14 @@ void forwardsManagedSettingsOnCreateAndResume() throws Exception {
assertTrue(json.contains("\"disableBypassPermissionsMode\":\"disable\""));
}
+ @Test
+ void acceptsFutureBypassPermissionsModes() throws Exception {
+ var permissions = new ManagedSettingsPermissions().setDisableBypassPermissionsMode("future-fail-closed-mode");
+ var json = new ObjectMapper().writeValueAsString(permissions);
+
+ assertTrue(json.contains("\"disableBypassPermissionsMode\":\"future-fail-closed-mode\""));
+ }
+
@Test
void preservesExplicitEmptyPermissionArrays() throws Exception {
// Security-critical: a present empty allow list admits nothing, while an
diff --git a/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java b/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java
index cf5b0426c5..602089d012 100644
--- a/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java
@@ -818,7 +818,8 @@ void modelsListResult_nested() {
var policy = new ModelPolicy(ModelPolicyState.ENABLED, null);
var promo = new ModelBillingPromo("summer-2026", 25.0, "2026-08-01T00:00:00Z", "Summer discount");
var billing = new ModelBilling(1.0, null, null, promo);
- var modelItem = new Model("gpt-5", "GPT-5", capabilities, policy, billing, null, null, null, null, null);
+ var modelItem = new Model("gpt-5", "GPT-5", capabilities, policy, billing, null, null, null, null, null, null,
+ null, null);
var result = new ModelsListResult(List.of(modelItem));
assertEquals(1, result.models().size());
From e70cc82775ddbb59ba74beec670b3103a8cfa4f4 Mon Sep 17 00:00:00 2001
From: Matt Ellis
Date: Tue, 25 Aug 2026 14:54:51 -0700
Subject: [PATCH 04/11] Adapt Java to Copilot 1.0.81-10 schema
Update handwritten constructor calls for the new sandbox, skill, and reasoning fields.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---
java/sdk/src/main/java/com/github/copilot/CopilotClient.java | 2 ++
.../test/java/com/github/copilot/SessionEventHandlingTest.java | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java
index cdd1b9ff3c..fe9a3c3b80 100644
--- a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java
+++ b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java
@@ -1277,10 +1277,12 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool
null, // shellInitProfile
null, // shellProcessFlags
null, // sandboxConfig
+ null, // sandboxConfigSource
null, // logInteractiveShells
null, // envValueMode
null, // allowAllMcpServerInstructions
null, // skillDirectories
+ null, // includedBuiltinSkills
null, // disabledSkills
null, // enableOnDemandInstructionDiscovery
null, // maxInlineBinaryBytes
diff --git a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java
index bd38d4962e..47d537f134 100644
--- a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java
@@ -865,7 +865,7 @@ private SessionStartEvent createSessionStartEvent(String sessionId) {
private AssistantMessageEvent createAssistantMessageEvent(String content) {
var event = new AssistantMessageEvent();
var data = new AssistantMessageEvent.AssistantMessageEventData(null, null, content, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
event.setData(data);
return event;
}
From 709dc4b36f1f231128ce6ea91d2efc6236c51012 Mon Sep 17 00:00:00 2001
From: Matt Ellis
Date: Tue, 25 Aug 2026 15:20:31 -0700
Subject: [PATCH 05/11] Adapt SDK tests to Copilot 1.0.81-10 auth schema
Use the regenerated MCP OAuth discriminator, settable auth inputs, and public Rust option construction pattern.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e98a14a6-7ad4-4cb6-b808-e56547701c19
---
dotnet/test/E2E/RpcSessionStateE2ETests.cs | 2 +-
python/copilot/session.py | 8 +++---
python/e2e/test_mcp_oauth_e2e.py | 4 +--
rust/tests/e2e/rpc_session_state.rs | 31 ++++++++++++----------
4 files changed, 24 insertions(+), 21 deletions(-)
diff --git a/dotnet/test/E2E/RpcSessionStateE2ETests.cs b/dotnet/test/E2E/RpcSessionStateE2ETests.cs
index 6dce3c250f..803c1c602b 100644
--- a/dotnet/test/E2E/RpcSessionStateE2ETests.cs
+++ b/dotnet/test/E2E/RpcSessionStateE2ETests.cs
@@ -451,7 +451,7 @@ public async Task Should_Set_Auth_Credentials()
});
var login = $"sdk-rpc-{Guid.NewGuid():N}";
- var setCredentials = await session.Rpc.GitHubAuth.SetCredentialsAsync(new AuthInfoUser
+ var setCredentials = await session.Rpc.GitHubAuth.SetCredentialsAsync(new SettableAuthInfoUser
{
CopilotUser = new CopilotUserResponse
{
diff --git a/python/copilot/session.py b/python/copilot/session.py
index 30a555a389..21c74bcaf5 100644
--- a/python/copilot/session.py
+++ b/python/copilot/session.py
@@ -34,11 +34,11 @@
CanvasProviderOpenResult,
ClientSessionApiHandlers,
CommandsHandlePendingCommandRequest,
+ GitHubTokenAcquireResultKind,
HandlePendingToolCallRequest,
LogRequest,
MCPOauthHandlePendingRequest,
MCPOauthPendingRequestResponse,
- MCPOauthPendingRequestResponseKind,
ModelSwitchToRequest,
PermissionDecision,
PermissionDecisionApproveOnce,
@@ -2279,14 +2279,14 @@ async def _execute_mcp_auth_and_respond(
if result and result.get("kind", "token") == "token":
rpc_result = MCPOauthPendingRequestResponse(
- kind=MCPOauthPendingRequestResponseKind.TOKEN,
+ kind=GitHubTokenAcquireResultKind.TOKEN,
access_token=result["accessToken"],
expires_in=result.get("expiresIn"),
token_type=result.get("tokenType"),
)
else:
rpc_result = MCPOauthPendingRequestResponse(
- kind=MCPOauthPendingRequestResponseKind.CANCELLED
+ kind=GitHubTokenAcquireResultKind.CANCELLED
)
await self.rpc.mcp.oauth.handle_pending_request(
MCPOauthHandlePendingRequest(
@@ -2300,7 +2300,7 @@ async def _execute_mcp_auth_and_respond(
MCPOauthHandlePendingRequest(
request_id=request_id,
result=MCPOauthPendingRequestResponse(
- kind=MCPOauthPendingRequestResponseKind.CANCELLED
+ kind=GitHubTokenAcquireResultKind.CANCELLED
),
)
)
diff --git a/python/e2e/test_mcp_oauth_e2e.py b/python/e2e/test_mcp_oauth_e2e.py
index 9d70597c3e..76f1cbf721 100644
--- a/python/e2e/test_mcp_oauth_e2e.py
+++ b/python/e2e/test_mcp_oauth_e2e.py
@@ -8,11 +8,11 @@
import pytest
from copilot.generated.rpc import (
+ GitHubTokenAcquireResultKind,
MCPAppsCallToolRequest,
MCPListToolsRequest,
MCPOauthHandlePendingRequest,
MCPOauthPendingRequestResponse,
- MCPOauthPendingRequestResponseKind,
)
from copilot.session import MCPServerConfig, PermissionHandler
from copilot.session_events import McpServerStatus
@@ -206,7 +206,7 @@ async def on_mcp_auth_request(request, _invocation):
MCPOauthHandlePendingRequest(
request_id=request["requestId"],
result=MCPOauthPendingRequestResponse(
- kind=MCPOauthPendingRequestResponseKind.TOKEN,
+ kind=GitHubTokenAcquireResultKind.TOKEN,
access_token=EXPECTED_TOKEN,
token_type="Bearer",
expires_in=3600,
diff --git a/rust/tests/e2e/rpc_session_state.rs b/rust/tests/e2e/rpc_session_state.rs
index 15db6e3a9a..64b61a94ec 100644
--- a/rust/tests/e2e/rpc_session_state.rs
+++ b/rust/tests/e2e/rpc_session_state.rs
@@ -762,18 +762,18 @@ async fn should_update_options_and_initialize_session_services() {
.await
.expect("create session");
+ let mut update_options = SessionUpdateOptionsParams::default();
+ update_options.ask_user_disabled = Some(true);
+ update_options.available_tools = Some(vec!["view".to_string()]);
+ update_options.client_name = Some("rust-rpc-e2e".to_string());
+ update_options.enable_streaming = Some(true);
+ update_options.model = Some(MODEL_ID.to_string());
+ update_options.working_directory = Some(ctx.work_dir().display().to_string());
+
let options = session
.rpc()
.options()
- .update(SessionUpdateOptionsParams {
- ask_user_disabled: Some(true),
- available_tools: Some(vec!["view".to_string()]),
- client_name: Some("rust-rpc-e2e".to_string()),
- enable_streaming: Some(true),
- model: Some(MODEL_ID.to_string()),
- working_directory: Some(ctx.work_dir().display().to_string()),
- ..SessionUpdateOptionsParams::default()
- })
+ .update(update_options)
.await
.expect("update options");
assert!(options.success);
@@ -893,11 +893,14 @@ async fn should_set_auth_credentials() {
.rpc()
.git_hub_auth()
.set_credentials(SessionSetCredentialsParams {
- credentials: Some(AuthInfo::User(UserAuthInfo {
- host: "github.com".to_string(),
- login: "rpc-session-user".to_string(),
- ..Default::default()
- })),
+ credentials: Some(
+ serde_json::to_value(AuthInfo::User(UserAuthInfo {
+ host: "github.com".to_string(),
+ login: "rpc-session-user".to_string(),
+ ..Default::default()
+ }))
+ .expect("serialize auth credentials"),
+ ),
})
.await
.expect("set credentials");
From 52b63db624f0842426024074614e66666ce8fb44 Mon Sep 17 00:00:00 2001
From: Matt Ellis
Date: Tue, 25 Aug 2026 15:38:26 -0700
Subject: [PATCH 06/11] Make managed bypass modes forward compatible in .NET
Use an open string property with well-known constants and cover unknown future modes on the wire.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e98a14a6-7ad4-4cb6-b808-e56547701c19
---
dotnet/src/Types.cs | 24 ++++++++--------
.../test/Unit/ClientSessionLifetimeTests.cs | 28 ++++++++++++++++++-
2 files changed, 39 insertions(+), 13 deletions(-)
diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs
index c0810b3870..3b5d3e56ca 100644
--- a/dotnet/src/Types.cs
+++ b/dotnet/src/Types.cs
@@ -3052,15 +3052,14 @@ public sealed class GitHubMcpToolConfig
public bool? DisableFormDeferral { get; set; }
}
-///
-/// Controls whether bypass-permissions mode is available in a managed session.
-///
-[JsonConverter(typeof(JsonStringEnumConverter))]
-public enum DisableBypassPermissionsMode
+/// Well-known managed bypass-permissions policies.
+public static class DisableBypassPermissionsModes
{
- /// Turn off bypass-permissions mode.
- [JsonStringEnumMemberName("disable")]
- Disable
+ /// Turns off bypass-permissions mode entirely.
+ public const string Disable = "disable";
+
+ /// Permits automatic bypass but blocks full allow-all.
+ public const string AllowAutoOnly = "allow-auto-only";
}
///
@@ -3077,12 +3076,13 @@ public enum DisableBypassPermissionsMode
public sealed class ManagedSettingsPermissions
{
///
- /// When set to "disable", bypass-permissions mode is turned off for the
- /// session regardless of other layers. Serialized as
- /// disableBypassPermissionsMode.
+ /// Restricts bypass-permissions mode for the session regardless of other
+ /// layers. See for well-known
+ /// values. Unknown values are forwarded so newer runtime policies fail closed.
+ /// Serialized as disableBypassPermissionsMode.
///
[JsonPropertyName("disableBypassPermissionsMode")]
- public DisableBypassPermissionsMode? DisableBypassPermissionsMode { get; set; }
+ public string? DisableBypassPermissionsMode { get; set; }
/// Tool-permission patterns that are always denied.
[JsonPropertyName("deny")]
diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs
index a561ee44b2..47ee14bb69 100644
--- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs
+++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs
@@ -552,7 +552,7 @@ public async Task CreateSessionAsync_Serializes_ManagedSettings_Permissions()
{
Permissions = new ManagedSettingsPermissions
{
- DisableBypassPermissionsMode = DisableBypassPermissionsMode.Disable,
+ DisableBypassPermissionsMode = DisableBypassPermissionsModes.Disable,
Deny = ["shell(rm*)"],
Ask = ["write"],
Allow = []
@@ -585,6 +585,32 @@ public async Task CreateSessionAsync_Serializes_ManagedSettings_Permissions()
Assert.True(invocation.ManagedSettingsEnabled);
}
+ [Fact]
+ public async Task CreateSessionAsync_Serializes_Future_ManagedSettings_Bypass_Mode()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ await client.StartAsync();
+
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ ManagedSettings = new ManagedSettings
+ {
+ Permissions = new ManagedSettingsPermissions
+ {
+ DisableBypassPermissionsMode = "future-fail-closed-mode"
+ }
+ },
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ var request = Assert.Single(server.Requests, request => request.Method == "session.create");
+ var permissions = request.Params.GetProperty("managedSettings").GetProperty("permissions");
+ Assert.Equal(
+ "future-fail-closed-mode",
+ permissions.GetProperty("disableBypassPermissionsMode").GetString());
+ }
+
[Fact]
public async Task PermissionResponse_Forwards_DecisionContext_As_Sibling_Of_Result()
{
From 67ef658f21b087de962bc183dc12cb918a283385 Mon Sep 17 00:00:00 2001
From: Matt Ellis
Date: Tue, 25 Aug 2026 15:47:58 -0700
Subject: [PATCH 07/11] Make Python managed bypass modes forward compatible
Expose well-known mode constants while accepting and forwarding future fail-closed runtime values.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e98a14a6-7ad4-6587-b885-95ed912cb84b
---
python/copilot/__init__.py | 2 ++
python/copilot/client.py | 17 ++++++++++++++---
python/test_client.py | 15 +++++++++++----
3 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py
index f7a71ebe91..8f30632e37 100644
--- a/python/copilot/__init__.py
+++ b/python/copilot/__init__.py
@@ -35,6 +35,7 @@
CloudSessionRepository,
CopilotClient,
CopilotExpAssignmentResponse,
+ DisableBypassPermissionsModes,
ExpConfigEntry,
ExpFlagValue,
GetAuthStatusResponse,
@@ -258,6 +259,7 @@
"ExitPlanModeResult",
"ExtensionInfo",
"CopilotWebSocketForwarder",
+ "DisableBypassPermissionsModes",
"GetAuthStatusResponse",
"BearerTokenProvider",
"GetStatusResponse",
diff --git a/python/copilot/client.py b/python/copilot/client.py
index 2654c14477..ad4b0fe171 100644
--- a/python/copilot/client.py
+++ b/python/copilot/client.py
@@ -247,6 +247,16 @@ def _capi_session_options_to_wire(options: CapiSessionOptions) -> dict[str, Any]
return wire
+class DisableBypassPermissionsModes:
+ """Well-known managed bypass-permissions policies."""
+
+ DISABLE: ClassVar[str] = "disable"
+ """Turn off bypass-permissions mode entirely."""
+
+ ALLOW_AUTO_ONLY: ClassVar[str] = "allow-auto-only"
+ """Permit automatic bypass but block full allow-all."""
+
+
@dataclass
class ManagedSettingsPermissions:
"""Permissions-only managed policy injected via :class:`ManagedSettings`.
@@ -256,9 +266,10 @@ class ManagedSettingsPermissions:
rules are rejected by the runtime at session creation.
"""
- disable_bypass_permissions_mode: Literal["disable"] | None = None
- """When ``"disable"``, turns off bypass-permissions ("yolo") mode for the
- session. Deny-wins: no other layer can re-enable it. Sent on the wire as
+ disable_bypass_permissions_mode: str | None = None
+ """Restricts bypass-permissions mode for the session. See
+ :class:`DisableBypassPermissionsModes` for well-known values. Unknown values
+ are forwarded so newer runtime policies fail closed. Sent on the wire as
``disableBypassPermissionsMode``."""
deny: list[str] | None = None
"""Operations that must always be denied. Unioned across managed layers."""
diff --git a/python/test_client.py b/python/test_client.py
index cf4bdf192b..8ed633640c 100644
--- a/python/test_client.py
+++ b/python/test_client.py
@@ -17,6 +17,7 @@
CanvasProviderIdentity,
CapiSessionOptions,
CopilotClient,
+ DisableBypassPermissionsModes,
ExtensionInfo,
ModelBillingTokenPrices,
ModelBillingTokenPricesLongContext,
@@ -721,7 +722,7 @@ async def mock_request(method, params, **kwargs):
enable_managed_settings=True,
managed_settings=ManagedSettings(
permissions=ManagedSettingsPermissions(
- disable_bypass_permissions_mode="disable",
+ disable_bypass_permissions_mode=DisableBypassPermissionsModes.ALLOW_AUTO_ONLY,
deny=["Shell(git push)"],
ask=["Domain(publish.example)"],
allow=["Read(**)"],
@@ -732,7 +733,10 @@ async def mock_request(method, params, **kwargs):
session.session_id,
on_permission_request=PermissionHandler.approve_all,
managed_settings=ManagedSettings(
- permissions=ManagedSettingsPermissions(ask=["Domain(publish.example)"])
+ permissions=ManagedSettingsPermissions(
+ disable_bypass_permissions_mode="future-fail-closed-mode",
+ ask=["Domain(publish.example)"],
+ )
),
)
@@ -741,14 +745,17 @@ async def mock_request(method, params, **kwargs):
assert captured["session.create"]["enableManagedSettings"] is True
assert captured["session.create"]["managedSettings"] == {
"permissions": {
- "disableBypassPermissionsMode": "disable",
+ "disableBypassPermissionsMode": "allow-auto-only",
"deny": ["Shell(git push)"],
"ask": ["Domain(publish.example)"],
"allow": ["Read(**)"],
}
}
assert captured["session.resume"]["managedSettings"] == {
- "permissions": {"ask": ["Domain(publish.example)"]}
+ "permissions": {
+ "disableBypassPermissionsMode": "future-fail-closed-mode",
+ "ask": ["Domain(publish.example)"],
+ }
}
finally:
await client.force_stop()
From 2a386ac1cafdb4df1c345ab513e5e408ab809579 Mon Sep 17 00:00:00 2001
From: Matt Ellis
Date: Tue, 25 Aug 2026 16:15:11 -0700
Subject: [PATCH 08/11] Align managed bypass modes in Node and Rust
Broaden the Node API to forward future fail-closed values and add Rust support for the allow-auto-only policy.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e98a14a6-7ad4-4cb6-b808-e56547701c19
---
nodejs/src/index.ts | 2 +-
nodejs/src/types.ts | 20 ++++++++++++++------
nodejs/test/client.test.ts | 32 +++++++++++++++++++++++---------
rust/src/types.rs | 11 ++++++-----
rust/tests/session_test.rs | 16 ++++++++++++++--
5 files changed, 58 insertions(+), 23 deletions(-)
diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts
index f91e351d30..ae474eefee 100644
--- a/nodejs/src/index.ts
+++ b/nodejs/src/index.ts
@@ -9,7 +9,7 @@
*/
export { CopilotClient } from "./client.js";
-export { RuntimeConnection } from "./types.js";
+export { DisableBypassPermissionsModes, RuntimeConnection } from "./types.js";
export { BuiltInTools, ToolSet } from "./toolSet.js";
export { CopilotSession, type AssistantMessageEvent } from "./session.js";
export { defineFactory, FactoryResumeError, isFactoryRunTerminal } from "./factory.js";
diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts
index 678cd58633..24d23d0826 100644
--- a/nodejs/src/types.ts
+++ b/nodejs/src/types.ts
@@ -2167,6 +2167,14 @@ export interface GitHubMcpToolConfig {
disableFormDeferral?: boolean;
}
+/** Well-known managed bypass-permissions policies. */
+export const DisableBypassPermissionsModes = {
+ /** Turn off bypass-permissions mode entirely. */
+ Disable: "disable",
+ /** Permit automatic bypass but block full allow-all. */
+ AllowAutoOnly: "allow-auto-only",
+} as const;
+
/**
* Permissions-only managed policy injected by the host via
* {@link SessionConfigBase.managedSettings}.
@@ -2177,11 +2185,11 @@ export interface GitHubMcpToolConfig {
*/
export interface ManagedSettingsPermissions {
/**
- * When set to `"disable"`, bypass-permissions ("yolo") mode is turned off
- * for the session. This is deny-wins: it cannot be re-enabled by any other
- * layer.
+ * Restricts bypass-permissions mode for the session. See
+ * {@link DisableBypassPermissionsModes} for well-known values. Unknown
+ * values are forwarded so newer runtime policies fail closed.
*/
- disableBypassPermissionsMode?: "disable";
+ disableBypassPermissionsMode?: string;
/** Operations that must always be denied. Unioned across managed layers. */
deny?: string[];
/**
@@ -2721,8 +2729,8 @@ export interface SessionConfigBase {
* with the same managed-permission parser it uses for fetched policy and
* composes it restrictively with any self-fetched (server) and
* device-managed (MDM) layers: `deny`/`ask` rules are unioned, every
- * declared `allow` list must admit an operation, and
- * `disableBypassPermissionsMode: "disable"` is deny-wins.
+ * declared `allow` list must admit an operation, and bypass-mode
+ * restrictions are composed fail-closed.
*
* This is startup-only. It is **not** persisted: it must be re-supplied on
* {@link CopilotClient.resumeSession | resume}, where it replaces the prior
diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts
index e2d630ba0e..34166163ec 100644
--- a/nodejs/test/client.test.ts
+++ b/nodejs/test/client.test.ts
@@ -10,8 +10,10 @@ import {
createAttributedPermissionResult,
CopilotClient,
createCanvas,
+ DisableBypassPermissionsModes,
RuntimeConnection,
type GitHubTelemetryNotification,
+ type ManagedSettings,
type ModelInfo,
} from "../src/index.js";
import { CopilotSession } from "../src/session.js";
@@ -3905,19 +3907,20 @@ describe("managedSettings serialization", () => {
}
it("forwards the full permissions object on session.create", async () => {
- const params = await captureCreateParams({
- managedSettings: {
- permissions: {
- disableBypassPermissionsMode: "disable",
- deny: ["Shell(git push)"],
- ask: ["Domain(publish.example)"],
- allow: ["Read(**)"],
- },
+ const managedSettings = {
+ permissions: {
+ disableBypassPermissionsMode: DisableBypassPermissionsModes.AllowAutoOnly,
+ deny: ["Shell(git push)"],
+ ask: ["Domain(publish.example)"],
+ allow: ["Read(**)"],
},
+ } satisfies ManagedSettings;
+ const params = await captureCreateParams({
+ managedSettings,
});
expect(params.managedSettings).toEqual({
permissions: {
- disableBypassPermissionsMode: "disable",
+ disableBypassPermissionsMode: "allow-auto-only",
deny: ["Shell(git push)"],
ask: ["Domain(publish.example)"],
allow: ["Read(**)"],
@@ -3925,6 +3928,17 @@ describe("managedSettings serialization", () => {
});
});
+ it("forwards unknown bypass-permissions modes", async () => {
+ const managedSettings = {
+ permissions: {
+ disableBypassPermissionsMode: "future-fail-closed-mode",
+ },
+ } satisfies ManagedSettings;
+ const params = await captureCreateParams({ managedSettings });
+
+ expect(params.managedSettings).toEqual(managedSettings);
+ });
+
it("marks directly injected sessions as managed", async () => {
const client = new CopilotClient();
await client.start();
diff --git a/rust/src/types.rs b/rust/src/types.rs
index afcb4d515b..9936127182 100644
--- a/rust/src/types.rs
+++ b/rust/src/types.rs
@@ -1767,6 +1767,9 @@ pub struct CopilotExpAssignmentResponse {
pub enum DisableBypassPermissionsMode {
/// Turn off bypass-permissions mode.
Disable,
+ /// Permit automatic bypass but block full allow-all.
+ #[serde(rename = "allow-auto-only")]
+ AllowAutoOnly,
}
/// Permission rules injected as a managed-settings layer at session bootstrap.
@@ -1775,15 +1778,13 @@ pub enum DisableBypassPermissionsMode {
/// layer. This layer composes restrictively with any server- or device-level
/// managed settings: [`deny`](Self::deny) and [`ask`](Self::ask) rules are
/// unioned across layers, every present [`allow`](Self::allow) list must admit a
-/// tool for it to be allowed, and
-/// [`disable_bypass_permissions_mode`](Self::disable_bypass_permissions_mode) is
-/// honored if any layer sets it (deny-wins).
+/// tool for it to be allowed, and bypass-mode restrictions compose to the most
+/// restrictive setting.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ManagedSettingsPermissions {
- /// When set to `"disable"`, bypass-permissions mode is turned off for the
- /// session regardless of other layers. Serialized as
+ /// Restricts bypass-permissions mode for the session. Serialized as
/// `disableBypassPermissionsMode`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disable_bypass_permissions_mode: Option,
diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs
index 69a65a558a..4631cd5c9b 100644
--- a/rust/tests/session_test.rs
+++ b/rust/tests/session_test.rs
@@ -788,6 +788,18 @@ async fn create_session_sends_canvas_wire_fields() {
timeout(TIMEOUT, create_handle).await.unwrap().unwrap();
}
+#[test]
+fn managed_bypass_permissions_modes_use_wire_values() {
+ assert_eq!(
+ serde_json::to_value(DisableBypassPermissionsMode::Disable).unwrap(),
+ serde_json::json!("disable")
+ );
+ assert_eq!(
+ serde_json::to_value(DisableBypassPermissionsMode::AllowAutoOnly).unwrap(),
+ serde_json::json!("allow-auto-only")
+ );
+}
+
#[tokio::test]
async fn create_and_resume_send_managed_settings_permissions() {
use github_copilot_sdk::types::ResumeSessionConfig;
@@ -796,7 +808,7 @@ async fn create_and_resume_send_managed_settings_permissions() {
let managed = ManagedSettings::default().with_permissions(
ManagedSettingsPermissions::default()
- .with_disable_bypass_permissions_mode(DisableBypassPermissionsMode::Disable)
+ .with_disable_bypass_permissions_mode(DisableBypassPermissionsMode::AllowAutoOnly)
.with_deny(vec!["shell(rm*)".to_string()])
.with_ask(vec!["write".to_string()])
.with_allow(vec![]),
@@ -821,7 +833,7 @@ async fn create_and_resume_send_managed_settings_permissions() {
assert_eq!(request["method"], "session.create");
assert_eq!(request["params"]["enableManagedSettings"], true);
let perms = &request["params"]["managedSettings"]["permissions"];
- assert_eq!(perms["disableBypassPermissionsMode"], "disable");
+ assert_eq!(perms["disableBypassPermissionsMode"], "allow-auto-only");
assert_eq!(perms["deny"][0], "shell(rm*)");
assert_eq!(perms["ask"][0], "write");
assert_eq!(perms["allow"], serde_json::json!([]));
From 737326409a393917064382325809d9285ab9f2ef Mon Sep 17 00:00:00 2001
From: Matt Ellis
Date: Tue, 25 Aug 2026 18:01:52 -0700
Subject: [PATCH 09/11] Make Rust managed bypass modes forward compatible
Replace the closed enum with an open string field and retain well-known policies as public constants.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e98a14a6-7ad4-4cb6-b808-e56547701c19
---
rust/src/types.rs | 30 +++++++++++++-----------------
rust/tests/session_test.rs | 17 +++++++++++------
2 files changed, 24 insertions(+), 23 deletions(-)
diff --git a/rust/src/types.rs b/rust/src/types.rs
index 9936127182..d7aa8a4c1b 100644
--- a/rust/src/types.rs
+++ b/rust/src/types.rs
@@ -1760,16 +1760,14 @@ pub struct CopilotExpAssignmentResponse {
pub assignment_context: String,
}
-/// Controls whether bypass-permissions mode is available in a managed session.
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "lowercase")]
-#[non_exhaustive]
-pub enum DisableBypassPermissionsMode {
- /// Turn off bypass-permissions mode.
- Disable,
+/// Well-known managed bypass-permissions policies.
+pub struct DisableBypassPermissionsModes;
+
+impl DisableBypassPermissionsModes {
+ /// Turn off bypass-permissions mode entirely.
+ pub const DISABLE: &'static str = "disable";
/// Permit automatic bypass but block full allow-all.
- #[serde(rename = "allow-auto-only")]
- AllowAutoOnly,
+ pub const ALLOW_AUTO_ONLY: &'static str = "allow-auto-only";
}
/// Permission rules injected as a managed-settings layer at session bootstrap.
@@ -1784,10 +1782,11 @@ pub enum DisableBypassPermissionsMode {
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ManagedSettingsPermissions {
- /// Restricts bypass-permissions mode for the session. Serialized as
- /// `disableBypassPermissionsMode`.
+ /// Restricts bypass-permissions mode for the session. See
+ /// [`DisableBypassPermissionsModes`] for well-known values. Unknown values
+ /// are forwarded so newer runtime policies fail closed.
#[serde(default, skip_serializing_if = "Option::is_none")]
- pub disable_bypass_permissions_mode: Option,
+ pub disable_bypass_permissions_mode: Option,
/// Tool-permission patterns that are always denied.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deny: Option>,
@@ -1801,11 +1800,8 @@ pub struct ManagedSettingsPermissions {
impl ManagedSettingsPermissions {
/// Sets the bypass-permissions policy for this managed layer.
- pub fn with_disable_bypass_permissions_mode(
- mut self,
- value: DisableBypassPermissionsMode,
- ) -> Self {
- self.disable_bypass_permissions_mode = Some(value);
+ pub fn with_disable_bypass_permissions_mode(mut self, value: impl Into) -> Self {
+ self.disable_bypass_permissions_mode = Some(value.into());
self
}
diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs
index 4631cd5c9b..bb9c330dff 100644
--- a/rust/tests/session_test.rs
+++ b/rust/tests/session_test.rs
@@ -22,7 +22,7 @@ use github_copilot_sdk::session_events::{
};
use github_copilot_sdk::types::{
CanvasProviderIdentity, CloudSessionOptions, CloudSessionRepository, CommandContext,
- CommandDefinition, CommandHandler, DeliveryMode, DisableBypassPermissionsMode,
+ CommandDefinition, CommandHandler, DeliveryMode, DisableBypassPermissionsModes,
ElicitationRequest, ElicitationResult, ExitPlanModeData, ExtensionInfo, ManagedSettings,
ManagedSettingsPermissions, MessageOptions, PermissionDecisionContext,
PermissionDecisionOutcome, PermissionDecisionSource, PermissionDecisionSurface, RequestId,
@@ -790,13 +790,18 @@ async fn create_session_sends_canvas_wire_fields() {
#[test]
fn managed_bypass_permissions_modes_use_wire_values() {
+ let known = ManagedSettingsPermissions::default()
+ .with_disable_bypass_permissions_mode(DisableBypassPermissionsModes::ALLOW_AUTO_ONLY);
assert_eq!(
- serde_json::to_value(DisableBypassPermissionsMode::Disable).unwrap(),
- serde_json::json!("disable")
+ serde_json::to_value(known).unwrap()["disableBypassPermissionsMode"],
+ "allow-auto-only"
);
+
+ let future = ManagedSettingsPermissions::default()
+ .with_disable_bypass_permissions_mode("future-fail-closed-mode");
assert_eq!(
- serde_json::to_value(DisableBypassPermissionsMode::AllowAutoOnly).unwrap(),
- serde_json::json!("allow-auto-only")
+ serde_json::to_value(future).unwrap()["disableBypassPermissionsMode"],
+ "future-fail-closed-mode"
);
}
@@ -808,7 +813,7 @@ async fn create_and_resume_send_managed_settings_permissions() {
let managed = ManagedSettings::default().with_permissions(
ManagedSettingsPermissions::default()
- .with_disable_bypass_permissions_mode(DisableBypassPermissionsMode::AllowAutoOnly)
+ .with_disable_bypass_permissions_mode(DisableBypassPermissionsModes::ALLOW_AUTO_ONLY)
.with_deny(vec!["shell(rm*)".to_string()])
.with_ask(vec!["write".to_string()])
.with_allow(vec![]),
From acf9241d6c51fd08ae0da3b6473297e745f22794 Mon Sep 17 00:00:00 2001
From: Matt Ellis
Date: Tue, 25 Aug 2026 18:19:48 -0700
Subject: [PATCH 10/11] Apply nightly Rust constant ordering
Match the repository's reorder_impl_items formatting configuration used by Linux CI.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e98a14a6-7ad4-4cb6-b808-e56547701c19
---
rust/src/types.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rust/src/types.rs b/rust/src/types.rs
index d7aa8a4c1b..06c0fc4e79 100644
--- a/rust/src/types.rs
+++ b/rust/src/types.rs
@@ -1764,10 +1764,10 @@ pub struct CopilotExpAssignmentResponse {
pub struct DisableBypassPermissionsModes;
impl DisableBypassPermissionsModes {
- /// Turn off bypass-permissions mode entirely.
- pub const DISABLE: &'static str = "disable";
/// Permit automatic bypass but block full allow-all.
pub const ALLOW_AUTO_ONLY: &'static str = "allow-auto-only";
+ /// Turn off bypass-permissions mode entirely.
+ pub const DISABLE: &'static str = "disable";
}
/// Permission rules injected as a managed-settings layer at session bootstrap.
From 4f00a31ea6d3cfcb3a7dd0e86442e34f24726b02 Mon Sep 17 00:00:00 2001
From: Matt Ellis
Date: Tue, 25 Aug 2026 21:55:14 -0700
Subject: [PATCH 11/11] Clarify managed bypass policy documentation
Document allow-auto-only, forward-compatible values, and most-restrictive policy composition across Go, Java, and .NET.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e98a14a6-7ad4-4cb6-b808-e56547701c19
---
dotnet/src/Types.cs | 4 ++--
go/types.go | 6 +++---
.../com/github/copilot/rpc/ManagedSettingsPermissions.java | 4 +++-
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs
index 3b5d3e56ca..54595bfed7 100644
--- a/dotnet/src/Types.cs
+++ b/dotnet/src/Types.cs
@@ -3070,8 +3070,8 @@ public static class DisableBypassPermissionsModes
/// This layer composes restrictively with any server- or device-level managed
/// settings: and rules are unioned across
/// layers, every present list must admit a tool for it to be
-/// allowed, and is honored if any
-/// layer sets it (deny-wins).
+/// allowed, and policies compose to
+/// the most restrictive setting.
///
public sealed class ManagedSettingsPermissions
{
diff --git a/go/types.go b/go/types.go
index c8cf72197b..ff77c9e0c7 100644
--- a/go/types.go
+++ b/go/types.go
@@ -1586,9 +1586,9 @@ const (
// accepts for fetched managed policy (e.g. "Read(**)", "Shell(git push *)");
// malformed rules are rejected by the runtime at session creation.
type ManagedSettingsPermissions struct {
- // DisableBypassPermissionsMode, when set to "disable", turns off
- // bypass-permissions ("yolo") mode for the session. Deny-wins: no other
- // layer can re-enable it.
+ // DisableBypassPermissionsMode restricts bypass-permissions mode for the
+ // session. See the DisableBypassPermissionsMode constants for known values.
+ // Newer values are forwarded unchanged so runtime policies remain fail-closed.
DisableBypassPermissionsMode DisableBypassPermissionsMode `json:"disableBypassPermissionsMode,omitempty"`
// Deny lists operations that must always be denied. Unioned across layers.
Deny []string `json:"deny,omitzero"`
diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java
index f857391dbc..6755d959b3 100644
--- a/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java
+++ b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java
@@ -31,7 +31,9 @@ public String getDisableBypassPermissionsMode() {
}
/**
- * Disables bypass/allow-all permission modes.
+ * Restricts bypass/allow-all permission modes. See
+ * {@link DisableBypassPermissionsModes} for known values. Newer values are
+ * forwarded unchanged so runtime policies remain fail-closed.
*
* @param value
* bypass-permissions policy