-
Notifications
You must be signed in to change notification settings - Fork 766
Description
Summary
When running Copilot CLI in --acp mode, the agent does not send session/request_permission requests to the client for tool calls (file writes, shell commands, etc.). Instead, operations are auto-approved internally. This prevents ACP clients from implementing their own permission UIs or policies.
Expected Behavior
Per the Agent Client Protocol specification, session/request_permission is a baseline method that agents should send to request user authorization for tool calls:
"Permissioned tool calls: A first-class handshake for sensitive operations where the agent asks the client to prompt the user before proceeding."
The expected protocol flow is:
Agent → Client: session/request_permission (with tool call details and permission options)
Client → Agent: response (user's decision: allow_once, allow_always, reject, etc.)
This allows the client (IDE, editor, CLI wrapper) to be the permission gatekeeper.
Actual Behavior
In --acp mode, Copilot CLI auto-approves tool operations without sending session/request_permission to the client. The client never receives permission requests and has no opportunity to prompt the user or apply its own permission policies.
Test Results
We wrote integration tests that verify whether each ACP agent sends session/request_permission for write operations. Results:
| Agent | Sends session/request_permission |
ACP Compliant |
|---|---|---|
Gemini CLI (--experimental-acp) |
✅ Yes | ✅ Yes |
Claude Code (via claude-code-acp) |
✅ Yes | ✅ Yes |
Codex (via codex-acp) |
✅ Yes | ✅ Yes |
Copilot CLI (--acp) |
❌ No | ❌ No |
Copilot is the only agent that does not send permission requests.
Test Code
The test uses a TrackingApproveHandler that records all incoming session/request_permission calls:
/// A permission handler that tracks whether permission was requested and auto-approves.
pub struct TrackingApproveHandler {
pub request_count: AtomicUsize,
pub permission_requested: AtomicBool,
}
#[async_trait]
impl PermissionHandler for TrackingApproveHandler {
async fn handle_permission(
&self,
request: &PermissionRequest,
) -> Result<PermissionOutcome, String> {
self.request_count.fetch_add(1, Ordering::SeqCst);
self.permission_requested.store(true, Ordering::SeqCst);
// Auto-approve: find allow_always > allow_once > first allow > first option
let selected = request
.options
.iter()
.find(|o| o.kind == "allow_always")
.or_else(|| request.options.iter().find(|o| o.kind == "allow_once"))
.or_else(|| request.options.iter().find(|o| o.kind.contains("allow")))
.or_else(|| request.options.first());
match selected {
Some(option) => Ok(PermissionOutcome::Selected {
option_id: option.option_id.clone(),
}),
None => Err("No permission options available".to_string()),
}
}
}The test for Copilot:
/// Test whether Copilot sends permission requests for write operations.
///
/// KNOWN LIMITATION: Copilot CLI does NOT send `session/request_permission` in
/// `--acp` mode. Instead, it auto-approves operations internally.
#[tokio::test]
async fn test_permission_requested_for_write() {
let config = AgentConfig::copilot();
let handler = TrackingApproveHandler::new();
let client = create_client_with_permission_handler(config, handler.clone())
.await
.expect("Failed to create client");
client.connect().await.expect("Failed to connect");
// Send a prompt that triggers a file write
let write_prompt = "Create a file called /tmp/test.txt with content 'test'";
let result = run_write_prompt_with_tracking(&client, &handler).await;
// Result: permission_requested=false, request_count=0, completed=true
// Copilot auto-approves without sending session/request_permission
assert!(!result.permission_requested);
assert!(result.completed);
}Output:
Copilot permission test: permission_requested=false, request_count=0, completed=true
Compare with Gemini CLI which correctly sends permission requests:
Gemini CLI permission test: permission_requested=true, request_count=1, completed=true
Reproduction Steps
-
Create an ACP client that implements
PermissionHandlerand logs incoming requests -
Start Copilot CLI in ACP mode:
copilot --acp
-
Send a prompt that triggers a file write:
{"jsonrpc":"2.0","id":1,"method":"session/prompt","params":{ "sessionId":"...", "prompt":[{"type":"text","text":"Create a file called /tmp/test.txt with content 'hello'"}] }} -
Observe the ACP message stream - no
session/request_permissionrequest is sent before the file is written -
Compare with Gemini CLI (
gemini --experimental-acp) which correctly sends permission requests
Why This Matters
ACP clients like Zed, VS Code extensions, Neovim plugins, and CLI tools rely on session/request_permission to:
- Present permission prompts to users in their native UI
- Implement "always allow" / "always deny" policies per tool or path
- Provide audit logs of what operations were approved
- Enforce security policies in enterprise environments
Without this, ACP clients cannot provide a consistent permission experience when using Copilot as a backend agent, and users have no control over what operations Copilot performs.
References
- ACP Protocol Overview - Documents
session/request_permissionas a baseline method - Zed Agent Settings - Shows how Zed uses
agent.always_allow_tool_actionsto control permission prompts
Environment
- Copilot CLI version: 0.0.372
- OS: macOS (also reproducible on Linux)