diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md
index c36b856a4..71fd9b4b1 100644
--- a/docs/features/custom-agents.md
+++ b/docs/features/custom-agents.md
@@ -54,7 +54,7 @@ const session = await client.createSession({
prompt: "You are a code editor. Make minimal, surgical changes to files as requested.",
},
],
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -71,7 +71,7 @@ client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approved"),
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
model="gpt-4.1",
custom_agents=[
{
@@ -284,7 +284,7 @@ const session = await client.createSession({
skills: ["markdown-lint"],
},
],
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
diff --git a/docs/features/hooks.md b/docs/features/hooks.md
index d8684d202..db9ad72ec 100644
--- a/docs/features/hooks.md
+++ b/docs/features/hooks.md
@@ -50,7 +50,7 @@ const session = await client.createSession({
onPostToolUse: async (input, invocation) => { /* ... */ },
// ... add only the hooks you need
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -61,12 +61,13 @@ const session = await client.createSession({
```python
from copilot import CopilotClient
+from copilot.session import PermissionRequestResult
client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: {"kind": "approved"},
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
hooks={
"on_session_start": on_session_start,
"on_pre_tool_use": on_pre_tool_use,
@@ -113,7 +114,7 @@ func main() {
OnPostToolUse: onPostToolUse,
},
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
- return copilot.PermissionRequestResult{Kind: "approved"}, nil
+ return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindApproved}, nil
},
})
_ = session
@@ -133,7 +134,7 @@ session, err := client.CreateSession(ctx, &copilot.SessionConfig{
// ... add only the hooks you need
},
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
- return copilot.PermissionRequestResult{Kind: "approved"}, nil
+ return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindApproved}, nil
},
})
```
@@ -251,7 +252,7 @@ const session = await client.createSession({
return { permissionDecision: "allow" };
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -261,6 +262,8 @@ const session = await client.createSession({
Python
```python
+from copilot.session import PermissionRequestResult
+
READ_ONLY_TOOLS = ["read_file", "glob", "grep", "view"]
async def on_pre_tool_use(input_data, invocation):
@@ -273,7 +276,7 @@ async def on_pre_tool_use(input_data, invocation):
return {"permissionDecision": "allow"}
session = await client.create_session(
- on_permission_request=lambda req, inv: {"kind": "approved"},
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
hooks={"on_pre_tool_use": on_pre_tool_use},
)
```
@@ -463,7 +466,7 @@ const session = await client.createSession({
return { permissionDecision: "allow" };
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -481,7 +484,7 @@ const session = await client.createSession({
return { permissionDecision: "allow" };
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -563,7 +566,7 @@ const session = await client.createSession({
return null;
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -575,6 +578,7 @@ const session = await client.createSession({
```python
import json, aiofiles
+from copilot.session import PermissionRequestResult
audit_log = []
@@ -626,7 +630,7 @@ async def on_session_end(input_data, invocation):
return None
session = await client.create_session(
- on_permission_request=lambda req, inv: {"kind": "approved"},
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
hooks={
"on_session_start": on_session_start,
"on_user_prompt_submitted": on_user_prompt_submitted,
@@ -661,7 +665,7 @@ const session = await client.createSession({
: null;
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -694,7 +698,7 @@ const session = await client.createSession({
return null;
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -705,6 +709,7 @@ const session = await client.createSession({
```python
import subprocess
+from copilot.session import PermissionRequestResult
async def on_session_end(input_data, invocation):
sid = invocation["session_id"][:8]
@@ -723,7 +728,7 @@ async def on_error_occurred(input_data, invocation):
return None
session = await client.create_session(
- on_permission_request=lambda req, inv: {"kind": "approved"},
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
hooks={
"on_session_end": on_session_end,
"on_error_occurred": on_error_occurred,
@@ -750,7 +755,7 @@ const session = await client.createSession({
return null;
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -774,7 +779,7 @@ const session = await client.createSession({
return null;
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -800,7 +805,7 @@ const session = await client.createSession({
};
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -826,7 +831,7 @@ const session = await client.createSession({
return null;
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -850,7 +855,7 @@ const session = await client.createSession({
return null;
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -871,7 +876,7 @@ const session = await client.createSession({
};
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -917,7 +922,7 @@ const session = await client.createSession({
return null;
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
@@ -927,6 +932,8 @@ const session = await client.createSession({
Python
```python
+from copilot.session import PermissionRequestResult
+
session_metrics = {}
async def on_session_start(input_data, invocation):
@@ -956,7 +963,7 @@ async def on_session_end(input_data, invocation):
return None
session = await client.create_session(
- on_permission_request=lambda req, inv: {"kind": "approved"},
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
hooks={
"on_session_start": on_session_start,
"on_user_prompt_submitted": on_user_prompt_submitted,
@@ -999,7 +1006,7 @@ const session = await client.createSession({
return null;
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
diff --git a/docs/features/image-input.md b/docs/features/image-input.md
index 6a25b312e..286414c91 100644
--- a/docs/features/image-input.md
+++ b/docs/features/image-input.md
@@ -48,7 +48,7 @@ await client.start();
const session = await client.createSession({
model: "gpt-4.1",
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
await session.send({
@@ -75,7 +75,7 @@ client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approved"),
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
model="gpt-4.1",
)
@@ -263,7 +263,7 @@ await client.start();
const session = await client.createSession({
model: "gpt-4.1",
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
const base64ImageData = "..."; // your base64-encoded image
@@ -293,7 +293,7 @@ client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approved"),
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
model="gpt-4.1",
)
diff --git a/docs/features/skills.md b/docs/features/skills.md
index 2d89d62a8..6db0d60f3 100644
--- a/docs/features/skills.md
+++ b/docs/features/skills.md
@@ -29,7 +29,7 @@ const session = await client.createSession({
"./skills/code-review",
"./skills/documentation",
],
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
// Copilot now has access to skills in those directories
@@ -50,7 +50,7 @@ async def main():
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: {"kind": "approved"},
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
model="gpt-4.1",
skill_directories=[
"./skills/code-review",
@@ -375,7 +375,7 @@ const session = await client.createSession({
prompt: "Focus on OWASP Top 10 vulnerabilities",
skills: ["security-scan", "dependency-check"],
}],
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
> [!NOTE]
@@ -396,7 +396,7 @@ const session = await client.createSession({
tools: ["*"],
},
},
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
```
diff --git a/docs/features/steering-and-queueing.md b/docs/features/steering-and-queueing.md
index e7f67457c..7858a7d3f 100644
--- a/docs/features/steering-and-queueing.md
+++ b/docs/features/steering-and-queueing.md
@@ -48,7 +48,7 @@ await client.start();
const session = await client.createSession({
model: "gpt-4.1",
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
// Start a long-running task
@@ -77,7 +77,7 @@ async def main():
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approved"),
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
model="gpt-4.1",
)
@@ -235,7 +235,7 @@ await client.start();
const session = await client.createSession({
model: "gpt-4.1",
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
// Send an initial task
@@ -269,7 +269,7 @@ async def main():
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approved"),
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
model="gpt-4.1",
)
@@ -476,7 +476,7 @@ You can use both patterns together in a single session. Steering affects the cur
```typescript
const session = await client.createSession({
model: "gpt-4.1",
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
// Start a task
@@ -502,7 +502,7 @@ await session.send({
```python
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approved"),
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
model="gpt-4.1",
)
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 4ee6bd298..182bd7a52 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -671,7 +671,7 @@ from copilot.session import PermissionRequestResult
client = CopilotClient()
-session = await client.create_session(on_permission_request=lambda req, inv: PermissionRequestResult(kind="approved"))
+session = await client.create_session(on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"))
# Subscribe to all events
unsubscribe = session.on(lambda event: print(f"Event: {event.type}"))
diff --git a/docs/integrations/microsoft-agent-framework.md b/docs/integrations/microsoft-agent-framework.md
index 2f9f1966a..8fea9aa07 100644
--- a/docs/integrations/microsoft-agent-framework.md
+++ b/docs/integrations/microsoft-agent-framework.md
@@ -217,7 +217,7 @@ const client = new CopilotClient();
const session = await client.createSession({
model: "gpt-4.1",
tools: [getWeather],
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
await session.sendAndWait({ prompt: "What's the weather like in Seattle?" });
@@ -521,7 +521,7 @@ const client = new CopilotClient();
const session = await client.createSession({
model: "gpt-4.1",
streaming: true,
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
session.on("assistant.message_delta", (event) => {
@@ -600,7 +600,7 @@ import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient();
const session = await client.createSession({
model: "gpt-4.1",
- onPermissionRequest: async () => ({ kind: "approved" }),
+ onPermissionRequest: async () => ({ kind: "approve-once" }),
});
const response = await session.sendAndWait({ prompt: "Explain this code" });
```
diff --git a/dotnet/README.md b/dotnet/README.md
index 6b76f3913..d5be3ab80 100644
--- a/dotnet/README.md
+++ b/dotnet/README.md
@@ -758,7 +758,7 @@ var session = await client.CreateSessionAsync(new SessionConfig
if (request.Kind == "shell")
{
// Deny shell commands
- return new PermissionRequestResult { Kind = PermissionRequestResultKind.DeniedInteractivelyByUser };
+ return new PermissionRequestResult { Kind = PermissionRequestResultKind.Rejected };
}
return new PermissionRequestResult { Kind = PermissionRequestResultKind.Approved };
@@ -768,13 +768,16 @@ var session = await client.CreateSessionAsync(new SessionConfig
### Permission Result Kinds
-| Value | Meaning |
-| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `PermissionRequestResultKind.Approved` | Allow the tool to run |
-| `PermissionRequestResultKind.DeniedInteractivelyByUser` | User explicitly denied the request |
-| `PermissionRequestResultKind.DeniedCouldNotRequestFromUser` | No approval rule matched and user could not be asked |
-| `PermissionRequestResultKind.DeniedByRules` | Denied by a policy rule |
-| `PermissionRequestResultKind.NoResult` | Leave the permission request unanswered (the SDK returns without calling the RPC). Not allowed for protocol v2 permission requests (will be rejected). |
+The `Kind` property must be one of the canonical `PermissionRequestResultKind` values. Approval decisions are present-tense — they describe the decision to apply, not the past-tense outcome reported back on `permission.completed` session events.
+
+| Value | Wire value | Meaning |
+| ------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `PermissionRequestResultKind.Approved` | `"approve-once"` | Allow this single request |
+| `PermissionRequestResultKind.Rejected` | `"reject"` | Deny the request |
+| `PermissionRequestResultKind.UserNotAvailable` | `"user-not-available"` | Deny the request because no user is available to confirm it |
+| `PermissionRequestResultKind.NoResult` | `"no-result"` | Leave the permission request unanswered (the SDK returns without calling the RPC). Not allowed for protocol v2 permission requests (will be rejected). |
+
+> The past-tense names `PermissionRequestResultKind.DeniedInteractivelyByUser`, `PermissionRequestResultKind.DeniedCouldNotRequestFromUser`, and `PermissionRequestResultKind.DeniedByRules` remain as `[Obsolete]` aliases for backward compatibility — prefer the canonical members above in new code.
### Resuming Sessions
diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs
index 0775280e8..a4651bb4a 100644
--- a/dotnet/src/Types.cs
+++ b/dotnet/src/Types.cs
@@ -613,13 +613,13 @@ public override void Write(Utf8JsonWriter writer, PermissionRequestResultKind va
public class PermissionRequestResult
{
///
- /// Permission decision kind.
+ /// Permission decision kind. Use the static members of
+ /// to construct values. Valid kinds are:
///
- /// - "approved" — the operation is allowed.
- /// - "denied-by-rules" — denied by configured permission rules.
- /// - "denied-interactively-by-user" — the user explicitly denied the request.
- /// - "denied-no-approval-rule-and-could-not-request-from-user" — no rule matched and user approval was unavailable.
- /// - "no-result" — leave the pending permission request unanswered.
+ /// - "approve-once" () — allow this single request.
+ /// - "reject" () — deny the request.
+ /// - "user-not-available" () — deny because no user is available to confirm.
+ /// - "no-result" () — leave the pending request unanswered (protocol v1 only; rejected by protocol v2 servers).
///
///
[JsonPropertyName("kind")]
diff --git a/go/README.md b/go/README.md
index 29760064c..be54e2152 100644
--- a/go/README.md
+++ b/go/README.md
@@ -606,7 +606,7 @@ session, err := client.CreateSession(context.Background(), &copilot.SessionConfi
if request.Kind == copilot.KindShell {
// Deny shell commands
- return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindDeniedInteractivelyByUser}, nil
+ return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindRejected}, nil
}
return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindApproved}, nil
@@ -616,13 +616,16 @@ session, err := client.CreateSession(context.Background(), &copilot.SessionConfi
### Permission Result Kinds
-| Constant | Meaning |
-| ---------------------------------------------------------- | --------------------------------------------------------------------------------------- |
-| `PermissionRequestResultKindApproved` | Allow the tool to run |
-| `PermissionRequestResultKindDeniedInteractivelyByUser` | User explicitly denied the request |
-| `PermissionRequestResultKindDeniedCouldNotRequestFromUser` | No approval rule matched and user could not be asked |
-| `PermissionRequestResultKindDeniedByRules` | Denied by a policy rule |
-| `PermissionRequestResultKindNoResult` | Leave the permission request unanswered (protocol v1 only; not allowed for protocol v2) |
+The `Kind` field must be one of the canonical `PermissionRequestResultKind` constants. Approval decisions are present-tense — they describe the decision to apply, not the past-tense outcome reported back on `permission.completed` session events.
+
+| Constant | Wire value | Meaning |
+| --------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------- |
+| `PermissionRequestResultKindApproved` | `"approve-once"` | Allow this single request |
+| `PermissionRequestResultKindRejected` | `"reject"` | Deny the request |
+| `PermissionRequestResultKindUserNotAvailable` | `"user-not-available"` | Deny the request because no user is available to confirm it |
+| `PermissionRequestResultKindNoResult` | `"no-result"` | Leave the permission request unanswered (protocol v1 only; rejected by protocol v2 servers) |
+
+> The past-tense names `PermissionRequestResultKindDeniedInteractivelyByUser`, `PermissionRequestResultKindDeniedCouldNotRequestFromUser`, and `PermissionRequestResultKindDeniedByRules` remain as deprecated aliases for backward compatibility — prefer the canonical constants above in new code.
### Resuming Sessions
diff --git a/nodejs/README.md b/nodejs/README.md
index a8ada97ed..ce75b58f2 100644
--- a/nodejs/README.md
+++ b/nodejs/README.md
@@ -843,25 +843,28 @@ const session = await client.createSession({
// request.fullCommandText — full shell command (for shell)
if (request.kind === "shell") {
- // Deny shell commands
- return { kind: "denied-interactively-by-user" };
+ // Deny shell commands, optionally telling the model why
+ return { kind: "reject", feedback: "Shell commands are not allowed." };
}
- return { kind: "approved" };
+ return { kind: "approve-once" };
},
});
```
### Permission Result Kinds
-| Kind | Meaning |
-| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
-| `"approved"` | Allow the tool to run |
-| `"denied-interactively-by-user"` | User explicitly denied the request |
-| `"denied-no-approval-rule-and-could-not-request-from-user"` | No approval rule matched and user could not be asked |
-| `"denied-by-rules"` | Denied by a policy rule |
-| `"denied-by-content-exclusion-policy"` | Denied due to a content exclusion policy |
-| `"no-result"` | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) |
+The handler must return one of the `PermissionDecision` shapes (or `{ kind: "no-result" }`). Approval scopes are present-tense — they describe the decision to apply, not the outcome reported back on session events:
+
+| Kind | Meaning | Extra fields |
+| ------------------------ | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
+| `"approve-once"` | Allow this single request | — |
+| `"approve-for-session"` | Allow this request and remember the approval for the rest of the session | `approval?` (rule to remember), `domain?` (for URL approvals) |
+| `"approve-for-location"` | Allow this request and persist the approval for this project location (git root or cwd) | `approval` (rule to persist), `locationKey` (location to persist under) |
+| `"approve-permanently"` | Allow this request and persist the approval across sessions (currently used for URL domains) | `domain` (URL domain to approve) |
+| `"reject"` | Deny the request | `feedback?` (optional string surfaced to the agent) |
+| `"user-not-available"` | Deny the request because no user is available to confirm it | — |
+| `"no-result"` | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) | — |
### Resuming Sessions
diff --git a/nodejs/docs/examples.md b/nodejs/docs/examples.md
index a3483d8d4..c4b8acb1c 100644
--- a/nodejs/docs/examples.md
+++ b/nodejs/docs/examples.md
@@ -561,12 +561,12 @@ const session = await joinSession({
onPermissionRequest: async (request) => {
if (request.kind === "shell") {
// request.fullCommandText has the shell command
- return { kind: "approved" };
+ return { kind: "approve-once" };
}
if (request.kind === "write") {
- return { kind: "approved" };
+ return { kind: "approve-once" };
}
- return { kind: "denied-by-rules" };
+ return { kind: "reject" };
},
});
```
diff --git a/python/README.md b/python/README.md
index 79f97cf04..ddf7b632f 100644
--- a/python/README.md
+++ b/python/README.md
@@ -587,9 +587,9 @@ def on_permission_request(
if request.kind.value == "shell":
# Deny shell commands
- return PermissionRequestResult(kind="denied-interactively-by-user")
+ return PermissionRequestResult(kind="reject")
- return PermissionRequestResult(kind="approved")
+ return PermissionRequestResult(kind="approve-once")
session = await client.create_session(
on_permission_request=on_permission_request,
@@ -605,19 +605,19 @@ async def on_permission_request(
) -> PermissionRequestResult:
# Simulate an async approval check (e.g., prompting a user over a network)
await asyncio.sleep(0)
- return PermissionRequestResult(kind="approved")
+ return PermissionRequestResult(kind="approve-once")
```
### Permission Result Kinds
-| `kind` value | Meaning |
-| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
-| `"approved"` | Allow the tool to run |
-| `"denied-interactively-by-user"` | User explicitly denied the request |
-| `"denied-no-approval-rule-and-could-not-request-from-user"` | No approval rule matched and user could not be asked (default when no kind is specified) |
-| `"denied-by-rules"` | Denied by a policy rule |
-| `"denied-by-content-exclusion-policy"` | Denied due to a content exclusion policy |
-| `"no-result"` | Leave the request unanswered (not allowed for protocol v2 permission requests) |
+The handler must return a `PermissionRequestResult` with one of the kinds declared by the `PermissionRequestResultKind` type. Approval decisions are present-tense — they describe the decision to apply, not the past-tense outcome reported back on `permission.completed` session events.
+
+| `kind` value | Meaning |
+| ---------------------- | ------------------------------------------------------------------------------------------- |
+| `"approve-once"` | Allow this single request |
+| `"reject"` | Deny the request |
+| `"user-not-available"` | Deny the request because no user is available to confirm it (the default) |
+| `"no-result"` | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) |
### Resuming Sessions
diff --git a/test/scenarios/callbacks/hooks/python/main.py b/test/scenarios/callbacks/hooks/python/main.py
index dbfceb22a..ba224ef24 100644
--- a/test/scenarios/callbacks/hooks/python/main.py
+++ b/test/scenarios/callbacks/hooks/python/main.py
@@ -2,13 +2,14 @@
import os
from copilot import CopilotClient
from copilot.client import SubprocessConfig
+from copilot.session import PermissionRequestResult
hook_log: list[str] = []
async def auto_approve_permission(request, invocation):
- return {"kind": "approved"}
+ return PermissionRequestResult(kind="approve-once")
async def on_session_start(input_data, invocation):
diff --git a/test/scenarios/callbacks/hooks/typescript/src/index.ts b/test/scenarios/callbacks/hooks/typescript/src/index.ts
index 2a5cde585..4ecd7ec33 100644
--- a/test/scenarios/callbacks/hooks/typescript/src/index.ts
+++ b/test/scenarios/callbacks/hooks/typescript/src/index.ts
@@ -11,7 +11,7 @@ async function main() {
try {
const session = await client.createSession({
model: "claude-haiku-4.5",
- onPermissionRequest: async () => ({ kind: "approved" as const }),
+ onPermissionRequest: async () => ({ kind: "approve-once" as const }),
hooks: {
onSessionStart: async () => {
hookLog.push("onSessionStart");
diff --git a/test/scenarios/callbacks/permissions/README.md b/test/scenarios/callbacks/permissions/README.md
index 19945235f..2fcb7a67c 100644
--- a/test/scenarios/callbacks/permissions/README.md
+++ b/test/scenarios/callbacks/permissions/README.md
@@ -12,7 +12,7 @@ This pattern is the foundation for:
1. **Enable `onPermissionRequest` handler** on the session config
2. **Track which tools requested permission** in a log array
-3. **Approve all permission requests** (return `kind: "approved"`)
+3. **Approve all permission requests** (return `kind: "approve-once"`)
4. **Send a prompt that triggers tool use** (e.g., listing files via glob)
5. **Print the permission log** showing which tools were approved
@@ -29,12 +29,12 @@ This pattern is the foundation for:
| Option | Value | Effect |
|--------|-------|--------|
-| `onPermissionRequest` | Log + approve | Records tool name, returns `approved` |
+| `onPermissionRequest` | Log + approve | Records tool name, returns `approve-once` |
| `hooks.onPreToolUse` | Auto-allow | No tool confirmation prompts |
## Key Insight
-The `onPermissionRequest` handler gives the integrator full control over which tools the agent can execute. By inspecting the request (tool name, arguments), you can implement allow/deny lists, require human approval for dangerous operations, or log every action for compliance. Returning `{ kind: "denied" }` blocks the tool from running.
+The `onPermissionRequest` handler gives the integrator full control over which tools the agent can execute. By inspecting the request (tool name, arguments), you can implement allow/deny lists, require human approval for dangerous operations, or log every action for compliance. Returning `{ kind: "reject" }` blocks the tool from running.
## Run
diff --git a/test/scenarios/callbacks/permissions/python/main.py b/test/scenarios/callbacks/permissions/python/main.py
index de788e5fb..677ca58d0 100644
--- a/test/scenarios/callbacks/permissions/python/main.py
+++ b/test/scenarios/callbacks/permissions/python/main.py
@@ -2,6 +2,7 @@
import os
from copilot import CopilotClient
from copilot.client import SubprocessConfig
+from copilot.session import PermissionRequestResult
# Track which tools requested permission
permission_log: list[str] = []
@@ -9,7 +10,7 @@
async def log_permission(request, invocation):
permission_log.append(f"approved:{request.tool_name}")
- return {"kind": "approved"}
+ return PermissionRequestResult(kind="approve-once")
async def auto_approve_tool(input_data, invocation):
diff --git a/test/scenarios/callbacks/permissions/typescript/src/index.ts b/test/scenarios/callbacks/permissions/typescript/src/index.ts
index 6a163bc27..8e72fc08b 100644
--- a/test/scenarios/callbacks/permissions/typescript/src/index.ts
+++ b/test/scenarios/callbacks/permissions/typescript/src/index.ts
@@ -15,7 +15,7 @@ async function main() {
model: "claude-haiku-4.5",
onPermissionRequest: async (request) => {
permissionLog.push(`approved:${request.toolName}`);
- return { kind: "approved" as const };
+ return { kind: "approve-once" as const };
},
hooks: {
onPreToolUse: async () => ({ permissionDecision: "allow" as const }),
diff --git a/test/scenarios/callbacks/user-input/python/main.py b/test/scenarios/callbacks/user-input/python/main.py
index 0c23e6b15..07a7eb40e 100644
--- a/test/scenarios/callbacks/user-input/python/main.py
+++ b/test/scenarios/callbacks/user-input/python/main.py
@@ -2,13 +2,14 @@
import os
from copilot import CopilotClient
from copilot.client import SubprocessConfig
+from copilot.session import PermissionRequestResult
input_log: list[str] = []
async def auto_approve_permission(request, invocation):
- return {"kind": "approved"}
+ return PermissionRequestResult(kind="approve-once")
async def auto_approve_tool(input_data, invocation):
diff --git a/test/scenarios/callbacks/user-input/typescript/src/index.ts b/test/scenarios/callbacks/user-input/typescript/src/index.ts
index 5964ce6c1..915008b68 100644
--- a/test/scenarios/callbacks/user-input/typescript/src/index.ts
+++ b/test/scenarios/callbacks/user-input/typescript/src/index.ts
@@ -11,7 +11,7 @@ async function main() {
try {
const session = await client.createSession({
model: "claude-haiku-4.5",
- onPermissionRequest: async () => ({ kind: "approved" as const }),
+ onPermissionRequest: async () => ({ kind: "approve-once" as const }),
onUserInputRequest: async (request) => {
inputLog.push(`question: ${request.question}`);
return { answer: "Paris", wasFreeform: true };
diff --git a/test/scenarios/tools/skills/python/main.py b/test/scenarios/tools/skills/python/main.py
index 3ec9fb2ee..a6d6bf2c0 100644
--- a/test/scenarios/tools/skills/python/main.py
+++ b/test/scenarios/tools/skills/python/main.py
@@ -4,6 +4,7 @@
from copilot import CopilotClient
from copilot.client import SubprocessConfig
+from copilot.session import PermissionRequestResult
async def main():
@@ -16,7 +17,7 @@ async def main():
skills_dir = str(Path(__file__).resolve().parent.parent / "sample-skills")
session = await client.create_session(
- on_permission_request=lambda _, __: {"kind": "approved"},
+ on_permission_request=lambda _, __: PermissionRequestResult(kind="approve-once"),
model="claude-haiku-4.5",
skill_directories=[skills_dir],
hooks={
diff --git a/test/scenarios/tools/skills/typescript/src/index.ts b/test/scenarios/tools/skills/typescript/src/index.ts
index de7f13568..36447d975 100644
--- a/test/scenarios/tools/skills/typescript/src/index.ts
+++ b/test/scenarios/tools/skills/typescript/src/index.ts
@@ -16,7 +16,7 @@ async function main() {
const session = await client.createSession({
model: "claude-haiku-4.5",
skillDirectories: [skillsDir],
- onPermissionRequest: async () => ({ kind: "approved" as const }),
+ onPermissionRequest: async () => ({ kind: "approve-once" as const }),
hooks: {
onPreToolUse: async () => ({ permissionDecision: "allow" as const }),
},
diff --git a/test/scenarios/tools/virtual-filesystem/python/main.py b/test/scenarios/tools/virtual-filesystem/python/main.py
index f7635c6c6..57b197509 100644
--- a/test/scenarios/tools/virtual-filesystem/python/main.py
+++ b/test/scenarios/tools/virtual-filesystem/python/main.py
@@ -2,6 +2,7 @@
import os
from copilot import CopilotClient, define_tool
from copilot.client import SubprocessConfig
+from copilot.session import PermissionRequestResult
from pydantic import BaseModel, Field
# In-memory virtual filesystem
@@ -39,7 +40,7 @@ def list_files() -> str:
async def auto_approve_permission(request, invocation):
- return {"kind": "approved"}
+ return PermissionRequestResult(kind="approve-once")
async def auto_approve_tool(input_data, invocation):
diff --git a/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts b/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts
index 4f7dadfd6..fa146da83 100644
--- a/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts
+++ b/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts
@@ -51,7 +51,7 @@ async function main() {
// Remove all built-in tools — only our custom virtual FS tools are available
availableTools: [],
tools: [createFile, readFile, listFiles],
- onPermissionRequest: async () => ({ kind: "approved" as const }),
+ onPermissionRequest: async () => ({ kind: "approve-once" as const }),
hooks: {
onPreToolUse: async () => ({ permissionDecision: "allow" as const }),
},