google-gemini / google-gemini/gemini-cli
ACP: no tool_call session update before request_permission
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
### What happened?
In ACP mode, when a tool requires confirmation, `runTool()` sends `session/request_permission` but never sends a preceding `tool_call` session update. The [if/else in runTool](https://github.com/google-gemini/gemini-cli/blob/1fd42802be781c79e08f62da8056fef0863a7c50/packages/cli/src/acp/acpClient.ts#L888-L958) treats them as mutually exclusive:
- If `confirmationDetails` is truthy: sends `request_permission` only (no `tool_call`)
- If `confirmationDetails` is falsy: sends `tool_call` only (no `request_permission`)
After permission is granted, it sends `tool_call_update` (completed), but the client never saw a `tool_call` (pending) to associate it with.
This is easiest to reproduce with MCP tools from `session/new`. Since ACP doesn't pass `trust` to `MCPServerConfig`, MCP tools always need confirmation via the policy engine. The debug log shows the expected path:
```
[MESSAGE_BUS] publish: {"type":"tool-confirmation-request",...}
[PolicyEngine.check] toolCall.name: mcp_lookup_get_code, stringifiedArgs: {}
[PolicyEngine.check] NO MATCH - using default decision: ask_user
```
But the client only sees:
```json
{"sessionUpdate": "tool_call_update", "status": "completed", "toolCallId": "mcp_lookup_get_code-..."}
```
No `tool_call` with status pending or in_progress is ever emitted.
### What did you expect to happen?
Per the [ACP tool calls spec](https://agentclientprotocol.com/protocol/tool-calls#creating), the Agent SHOULD report a `tool_call` session update when the model requests a tool invocation. This should happen before [requesting permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission), so the client can track the tool call lifecycle:
```
tool_call (pending) -> request_permission -> tool_call_update (completed)
```
The fix is to send `tool_call` in both branches of the if/else, not just the else. Something like:
```typescript
// Always notify the client about the tool call first
await this.sendUpdate({
sessionUpdate: 'tool_call',
toolCallId: callId,
status: confirmationDetails ? 'pending' : 'in_progress',
title: invocation.getDescription(),
content: [],
locations: invocation.toolLocations(),
kind: toAcpToolKind(tool.kind),
});
if (confirmationDetails) {
// then request permission...
}
```
Note: #14957 shows `tool_call` with `in_progress` was being emitted for MCP tools in earlier versions, so this may be a regression from the confirmation refactor.
### Client information
Client Information
Built from source, commit 1fd42802b.
Platform: macOS (Darwin, aarch64)
### Login information
Google account.
### Anything else we need to know?
How to validate:
1. Start gemini in ACP mode with an MCP server via `session/new`
2. Prompt to use an MCP tool
3. Observe: client receives `session/request_permission` but no preceding `tool_call` session update
4. After approval, client receives `tool_call_update` (completed) with no matching `tool_call`
Related: #14957 (tool_call title issue, which shows MCP tools were emitting tool_call in earlier versions), #17952 (30s confirmation delay, same code path)
Contributor guide
Assessment
This issue has not been assessed yet.