Expose managed approval requirement on permission requests - #2080
Expose managed approval requirement on permission requests#2080joshspicer wants to merge 23 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Exposes managed approval requirements to TypeScript permission handlers.
Changes:
- Adds optional
managedApprovalRequiredmetadata. - Adds package-root type coverage.
Show a summary per file
| File | Description |
|---|---|
nodejs/src/types.ts |
Overlays managed approval metadata onto permission requests. |
nodejs/test/session-event-types.test.ts |
Verifies the field is publicly importable. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 3
- Review effort level: Medium
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 18f1bbc1-6001-43e2-b293-724505087f6a
13c3d37 to
03184d2
Compare
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (1)
nodejs/src/types.ts:1108
- This overlay only updates the standalone type and
PermissionHandler; the publicSessionEvent/PermissionRequestedDatatypes still come from the generated schema, wherepermissionRequestlacks this property. Consequently, an event-only host (the documented path whenonPermissionRequestis omitted) cannot compileevent.data.permissionRequest.managedApprovalRequiredeven though the runtime sends it. Please overlay thepermission.requestedevent payload as well so every public permission-request surface exposes the metadata.
export type PermissionRequest = GeneratedPermissionRequest & {
readonly managedApprovalRequired?: boolean;
};
- Files reviewed: 2/2 changed files
- Comments generated: 0 new
- Review effort level: Medium
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c7f00b84-b0a7-4cdf-aca9-ffd49737f26e
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (1)
nodejs/src/types.ts:1145
- The behavior change leaves the public README inaccurate:
nodejs/README.md:39,:133, and:856-866still describeapproveAllas allowing every request/tool call. With a managed request it now returnsno-result, so following those docs can leave execution pending until the consumer explicitly resolves the permission. Please update the permission-handling docs and examples to describe this exception and how hosts should resolve it.
export const approveAll: PermissionHandler = (request) =>
request.managedApprovalRequired ? { kind: "no-result" } : { kind: "approve-once" };
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Medium
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (1)
nodejs/README.md:869
- The immediately following custom-handler example still returns
approve-oncefor every non-shell request without checkingmanagedApprovalRequired. Copying it therefore auto-approves managed Read, Edit, or Domain asks, contradicting the human-approval requirement documented here. Update that example to route flagged requests through a human confirmation flow (or leave them unanswered).
For requests with `managedApprovalRequired: true`, `approveAll` returns `{ kind: "no-result" }`. The request remains pending and the host must present a human-facing confirmation flow to resolve it explicitly.
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Medium
|
Linked end-to-end validation completed against SDK head Cross-layer audit of the latest runtime session and AHP transcript confirmed:
The assistant's final table overstated downstream tool success for two approved Domain asks (HTTP redirect/404) and mislabeled the |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
AI Edit: Implemented the requested design in Validated focused permission tests and formatting/lint/build checks across Node, Go, Python, .NET, Java, and Rust. No generated files changed. |
There was a problem hiding this comment.
Review details
Files not reviewed (1)
- go/rpc/zsession_events.go: Generated file
Comments suppressed due to low confidence (17)
nodejs/src/types.ts:1134
approveAllis keyed to the session-wide managed-settings switch, so it throws for ordinary requests in managed sessions instead of leaving onlymanagedApprovalRequired: truerequests pending. This contradicts the public contract in the PR description and the README's request-level behavior; inspect the request flag and returnno-resultonly for managed asks.
export const approveAll: PermissionHandler = (_request, invocation) => {
if (invocation.managedSettingsEnabled) {
throw new Error("approveAll cannot be used when managed settings are enabled");
}
return { kind: "approve-once" };
dotnet/src/PermissionHandlers.cs:19
ApproveAllcurrently fails every request when managed settings are enabled, rather than suppressing only requests whoseManagedApprovalRequiredmetadata is true. This blocks automatic approval of ordinary requests in managed sessions and does not leave managed asks pending as the public contract requires.
(_, invocation) => invocation.ManagedSettingsEnabled
? Task.FromException<PermissionDecision>(
new InvalidOperationException("ApproveAll cannot be used when managed settings are enabled"))
: Task.FromResult(PermissionDecision.ApproveOnce());
java/src/main/java/com/github/copilot/rpc/PermissionHandler.java:58
APPROVE_ALLuses the session-wide managed-settings bit, so it fails ordinary requests in managed sessions instead of returningnoResult()only for requests withgetManagedApprovalRequired() == true. This conflicts with the stated public contract and leaves managed asks handled as errors rather than pending.
PermissionHandler APPROVE_ALL = (request, invocation) -> invocation.isManagedSettingsEnabled()
? CompletableFuture.failedFuture(
new IllegalStateException("APPROVE_ALL cannot be used when managed settings are enabled"))
: CompletableFuture.completedFuture(PermissionRequestResult.approveOnce());
rust/src/handler.rs:296
ApproveAllHandlerrejects all requests in a managed-settings session, including ordinary requests, and converts that rejection into an error response. It should inspectmanaged_approval_requiredand returnNoResultonly for managed asks so those remain pending for a human decision.
rust/src/permission.rs:127- This condition only guards
ApproveAlland does so at session scope. As a result,approve_if(|_| true)still auto-approvesmanaged_approval_required: true, while ordinary requests in a managed session error. Apply the request-level managed flag to every approving policy outcome and returnNoResult, retaining automatic rejection when the predicate denies.
rust/README.md:431 - This second policy section repeats the incorrect session-wide error behavior. The policy must abstain only for an individual request carrying
managed_approval_required: true, while ordinary requests remain auto-approved.
rust/src/handler.rs:49 - Adding
Errorto this public, exhaustive enum is a source-breaking API change for downstreammatchexpressions. It is also only used to implement the incorrect session-wide approve-all failure; the managed-request path already hasNoResult, so remove this variant and its payload mapping rather than expanding the public enum.
nodejs/README.md:869 - This documents a session-wide throw, but the contract is request-level: ordinary requests should still be approved in managed sessions, while
managedApprovalRequired: truereturnsno-result. Keeping this text would instruct hosts to avoid a supported configuration and contradict the behavior described earlier in this README.
When `enableManagedSettings` is true for the session, `approveAll` throws. Use a custom handler for managed sessions; request-level `managedApprovalRequired` remains available for human-facing confirmation logic.
python/README.md:795
- This describes a session-wide exception, but managed approval is request-level.
approve_allshould continue approving ordinary requests and returnPermissionNoResultonly whenmanaged_approval_requiredis true; document that behavior consistently with the custom-handler example.
When `enable_managed_settings` is true for the session, `approve_all` raises an error. Use a custom handler for managed sessions; request-level `managed_approval_required` remains available for human-facing confirmation logic.
go/README.md:218
- This says
ApproveAllerrors whenever managed settings are enabled, contradicting the request-level behavior documented below at line 686. The helper should approve ordinary requests and returnPermissionDecisionNoResultonly for requests whereRequiresManagedApproval()is true.
- `OnPermissionRequest` (PermissionHandlerFunc): Optional handler called before each tool execution to approve or deny it. When nil, permission requests are emitted as events and left pending for manual resolution. `copilot.PermissionHandler.ApproveAll` approves requests when managed settings are disabled and returns an error when `EnableManagedSettings` is true. Custom handlers can inspect `RequiresManagedApproval()` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section.
dotnet/README.md:128
- This documents a session-wide exception, while the same README later says a managed request returns
NoResult. The public contract is request-level:ApproveAllshould approve ordinary requests and abstain only when that request'sManagedApprovalRequiredflag is true.
- `OnPermissionRequest` - Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. `PermissionHandler.ApproveAll` approves requests when managed settings are disabled and throws when `EnableManagedSettings` is true. Custom handlers can inspect `ManagedApprovalRequired` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section.
java/README.md:125
- This describes
APPROVE_ALLfailing based on the session switch, but managed approval is carried by each request. It should document that ordinary requests are approved and requests withgetManagedApprovalRequired() == trueyieldnoResult().
`PermissionHandler.APPROVE_ALL` approves requests when managed settings are disabled. When `enableManagedSettings` is true, it completes exceptionally. Custom handlers can inspect `request.getManagedApprovalRequired()` for human-facing confirmation logic.
rust/README.md:251
- This documents a session-wide error rather than the request-level managed-approval contract.
ApproveAllHandlershould approve ordinary requests and returnNoResultonly whenmanaged_approval_requiredis true, including in managed-settings sessions.
This issue also appears on line 431 of the same file.
nodejs/src/types.ts:1129
- This JSDoc describes the same incorrect session-wide restriction as the implementation. The helper's contract is to approve ordinary requests in any session and abstain only for a request carrying
managedApprovalRequired: true.
This issue also appears on line 1130 of the same file.
/**
* Approves permission requests for sessions without managed settings.
*/
dotnet/src/PermissionHandlers.cs:14
- This public summary incorrectly makes the session switch the approval criterion.
ApproveAllshould approve ordinary requests and returnNoResultonly for an individual request whose managed-approval metadata is true.
This issue also appears on line 16 of the same file.
/// <summary>
/// A permission handler that approves requests when managed settings are disabled.
/// </summary>
rust/src/handler.rs:278
- This public API documentation describes a session-wide restriction, but managed approval is a property of each request. Document that ordinary requests are approved and requests with
managed_approval_required == Some(true)yieldNoResult.
This issue also appears on line 290 of the same file.
java/src/main/java/com/github/copilot/rpc/PermissionHandler.java:51
- This Javadoc documents the incorrect session-wide behavior.
APPROVE_ALLshould approve ordinary requests and returnnoResult()only when the current request requires managed approval.
This issue also appears on line 55 of the same file.
/**
* A pre-built handler that approves permission requests when managed settings
* are disabled.
- Files reviewed: 39/40 changed files
- Comments generated: 2
- Review effort level: Medium
Preserve approve-all fail-closed behavior by logging and returning the existing user-not-available decision instead of adding a public enum variant. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Run status: pushed |
There was a problem hiding this comment.
Review details
Files not reviewed (1)
- go/rpc/zsession_events.go: Generated file
Comments suppressed due to low confidence (7)
rust/README.md:431
- The approve-all policy does not return an error;
PermissionHandlerreturnsPermissionResult, and this path logs the misconfiguration then producesUserNotAvailable. The current wording gives users the wrong contract for handling the outcome.
rust/src/permission.rs:128 Policy::Predicatestill reachesapprove_once()when its predicate returnstruefor a request withmanaged_approval_required == Some(true). This reintroduces the managed-ask bypass that the earlier review thread says was fixed:approve_permissions_if(...)can automatically approve a request that requires an explicit human decision. ReturnNoResultfor any approving predicate outcome carrying the request-level flag, while preserving predicate denials and the intentional session-wideApproveAllmisconfiguration error; update the test that currently asserts this unsafe approval.
rust/src/permission.rs:25- This public helper documentation says managed requests remain pending, but the implementation below returns
UserNotAvailablefor the first permission dispatch whenever managed settings are enabled, regardless of the request flag. That session-wide failure is also the behavior documented inrust/README.md; describe it here so API users do not expectapprove_all()to abstain withNoResult.
go/README.md:686 - This contradicts both
PermissionHandler.ApproveAlland the configuration reference above: the helper checksManagedSettingsEnabledand returns an error before consultingRequiresManagedApproval(), so it never returnsPermissionDecisionNoResult. Document the session-wide error behavior; reserveNoResultfor the custom-handler example.
When `RequiresManagedApproval()` returns `true`, `ApproveAll` returns `PermissionDecisionNoResult`. The request remains pending and the host must present a human-facing confirmation flow to resolve it explicitly.
dotnet/README.md:790
- This describes behavior the implementation does not have:
PermissionHandler.ApproveAllthrows wheneverManagedSettingsEnabledis true and never returnsPermissionDecision.NoResult(). Align this section with the configuration reference and implementation, leaving request-level abstention to custom handlers.
When `ManagedApprovalRequired` is `true`, `ApproveAll` returns `PermissionDecision.NoResult()`. The request remains pending and the host must present a human-facing confirmation flow to resolve it explicitly.
nodejs/README.md:133
- This implies that
approveAllleaves managed requests pending, but the implementation throws on every permission dispatch whenenableManagedSettingsis true. The detailed permission section correctly documents that session-wide error. Make this configuration summary match it so users do not selectapproveAllexpecting request-level abstention.
- `onPermissionRequest?: PermissionHandler` - Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. Use `approveAll` to approve ordinary requests automatically; requests with `managedApprovalRequired: true` remain pending for explicit resolution through a human-facing host flow. Provide a custom function for other fine-grained control. See [Permission Handling](#permission-handling) section.
rust/README.md:251
ApproveAllHandler::handlecannot return an error—its return type isPermissionResult. With managed settings enabled,permission_handler_failurelogs an error and returns aUserNotAvailabledecision. Describe that observable result rather than promising an error value callers cannot receive.
This issue also appears on line 431 of the same file.
- Files reviewed: 39/40 changed files
- Comments generated: 0 new
- Review effort level: Medium
Align Rust approve-all paths for request-level managed approval and correct cross-SDK documentation of session-level fail-fast behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Files not reviewed (1)
- go/rpc/zsession_events.go: Generated file
Comments suppressed due to low confidence (1)
java/README.md:70
- The Gradle snapshot example no longer matches this module's snapshot version.
pom.xmlnow publishes1.0.10-preview.0-SNAPSHOT, while this coordinate points to the previous release line and adds an unsupported-01suffix, so users cannot consume the snapshot built by this branch.
implementation 'com.github:copilot-sdk-java:1.0.9-preview.0-01-SNAPSHOT'
- Files reviewed: 39/40 changed files
- Comments generated: 0 new
- Review effort level: Medium
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Files not reviewed (1)
- go/rpc/zsession_events.go: Generated file
Comments suppressed due to low confidence (4)
rust/src/permission.rs:27
- The final sentence contradicts the behavior documented immediately above and implemented by this handler: with managed settings enabled,
approve_all()returnsuser-not-availablebefore checkingmanaged_approval_required, so managed requests do not remain pending. Remove this sentence so callers do not rely on the built-in handler to leave these requests unanswered.
nodejs/README.md:883 - This example now recommends
no-resultfor managed requests, but the permission-result table below still saysno-resultis only valid with protocol v1 and is rejected by v2 servers. That directly contradicts the new managed-approval guidance and leaves consumers unable to tell whether this flow is supported; update the result-kind documentation to explain the current response-suppression behavior and any protocol limitation.
if ("managedApprovalRequired" in request && request.managedApprovalRequired === true) {
// Leave the request pending for the host's human-facing confirmation flow.
return { kind: "no-result" };
python/README.md:812
- This example now recommends
PermissionNoResultfor managed requests, but the permission-result table below still says that sentinel is only valid with protocol v1 and rejected by v2 servers. That contradicts the new managed-approval guidance; update the result-kind documentation to describe the current response-suppression behavior and any remaining protocol limitation.
if getattr(request, "managed_approval_required", False) is True:
return PermissionNoResult()
rust/src/handler.rs:284
- This claims managed requests remain pending, but
ApproveAllHandlerfirst returnsuser-not-availablewhenever managed settings are enabled. Since managed approval metadata is produced by managed settings, the public API documentation describes an outcome the built-in handler does not provide.
- Files reviewed: 37/38 changed files
- Comments generated: 0 new
- Review effort level: Medium
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Review details
Files not reviewed (1)
- go/rpc/zsession_events.go: Generated file
Comments suppressed due to low confidence (2)
java/src/main/java/com/github/copilot/rpc/PermissionHandler.java:22
- This managed-request example cannot leave the request pending on the still-supported protocol-v2 path.
RpcHandlerDispatcher.handlePermissionRequestexplicitly rejectsno-resultand converts it touser-not-available(RpcHandlerDispatcher.java:222-237), so a host following this guidance denies the managed request instead of allowing the required human decision. The v2 dispatch path needs compatible abstention behavior (and its existing rejection test updated) beforenoResult()can be recommended unconditionally here.
* if (Boolean.TRUE.equals(request.getManagedApprovalRequired())) {
* return CompletableFuture.completedFuture(PermissionRequestResult.noResult());
* }
nodejs/src/types.ts:1134
- The PR description still says built-in approve-all handlers approve ordinary requests and return
no-resultonly for managed requests, but this implementation throws for every request whenever the session enables managed settings. Since the prior review established that this session-wide failure is intentional, update the PR description to state that contract so reviewers and release notes do not advertise the superseded request-level behavior.
export const approveAll: PermissionHandler = (_request, invocation) => {
if (invocation.managedSettingsEnabled) {
throw new Error("approveAll cannot be used when managed settings are enabled");
}
return { kind: "approve-once" };
- Files reviewed: 37/38 changed files
- Comments generated: 0 new
- Review effort level: Medium
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Why
Enterprise-managed permission
askrules must be answered by a person. SDK hosts need to distinguish those requests from ordinary permission prompts that client settings may auto-approve.This is the public SDK contract for:
The initial runtime scope supports managed Shell, Read, Edit, and Domain selectors. Tool and MCP selectors are deferred.
What
Exposes optional managed-approval metadata on public permission request and
permission.requestedevent paths in every SDK:managedApprovalRequiredmanaged_approval_requiredManagedApprovalRequiredandRequiresManagedApproval()ManagedApprovalRequiredmanaged_approval_requiredgetManagedApprovalRequired()andPermissionRequest.fromJsonValue(...)for generated event valuesBuilt-in approve-all handlers continue approving requests when managed settings are disabled. When managed settings are enabled for a session, they fail loudly on the first permission dispatch so hosts without a human-facing permission UI do not silently leave managed asks hanging. Custom handlers can inspect the request-level managed flag to obtain an explicit human decision before approving affected requests.
Python, Go, and Rust codegen augment the pinned CLI schema until the runtime schema update ships in the repository's published CLI dependency. Regeneration is deterministic and the committed generated diff contains only the managed approval field.
Validation
go test ./...,golangci-lint run ./..., and Go docs validation-D warnings, nightly rustfmt, and reproducible codegen; the full replay E2E run had two unrelated snapshot mismatchesmvn verify, focused permission/dispatcher tests, Spotless, Checkstyle, and Java docs validation