ACP: permission rejection not reported to client

,

Where does the bug appear (feature/product)?

Cursor CLI

Describe the Bug

When a tool is rejected internally, tool_call_update still sends status: "completed" instead of "failed". Also, session/request_permission is never sent, so the client can’t present a permission dialog at all.

Steps to Reproduce

curl https://cursor.com/install -fsS | bash
npm install @agentclientprotocol/sdk

Run below as node reject-write-permission.mjs:

import * as acp from "@agentclientprotocol/sdk";
import { spawn } from "node:child_process";
import { existsSync, mkdirSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { Readable, Writable } from "node:stream";

const cwd = join(tmpdir(), `repro-${Date.now()}`);
mkdirSync(cwd, { recursive: true });
const testFile = join(cwd, "test.txt");

const child = spawn("cursor-agent", ["acp"], { stdio: ["pipe", "pipe", "inherit"] });
const stream = acp.ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));

const updates = [];
const client = new acp.ClientSideConnection(() => ({
  sessionUpdate: async (p) => {
    updates.push(p);
    if (p.update.sessionUpdate === "tool_call_update") console.log(`tool_call_update: status="${p.update.status}"`);
  },
  requestPermission: async (p) => {
    const reject = p.options.find((o) => o.kind === "reject_once");
    console.log(`request_permission: rejecting with "${reject.optionId}"`);
    return { outcome: { outcome: "selected", optionId: reject.optionId } };
  },
}), stream);

await client.initialize({ protocolVersion: acp.PROTOCOL_VERSION, clientCapabilities: { fs: { readTextFile: true, writeTextFile: false } }, clientInfo: { name: "repro", version: "1.0.0" } });
const { sessionId } = await client.newSession({ cwd, mcpServers: [] });
await client.prompt({ sessionId, prompt: [{ type: "text", text: `Write "should not see this" to ${testFile}` }] });

const hasFailed = updates.some((u) => u.update.sessionUpdate === "tool_call_update" && u.update.status === "failed");
console.log(`\nstatus "failed" found: ${hasFailed ? "YES" : "NO (bug)"}`);
console.log(`File written: ${existsSync(testFile)}`);
child.kill(); process.exit(0);

request_permission never fires. All tool_call_update messages have status: "completed" regardless of outcome. The file isn’t written (the internal rejection works), but the client has no visibility into any of it.

Expected Behavior

request_permission fires, client rejects, tool_call_update has status: "failed".

Operating System

MacOS

Version Information

2026.02.27-e7d2ef6

Additional Information

This breaks any ACP client trying to show a permission dialog or track tool outcomes.

Does this stop you from using Cursor

Sometimes - I can sometimes use Cursor

Hey, thanks for the detailed repro. This is a great bug report.

As I understand it, there are two issues:

  1. tool_call_update reports status: "completed" even when the tool call is rejected internally. It should be "failed".
  2. session/request_permission is never sent to the ACP client, so there’s no way to show the permission request dialog.

I’ve shared this with the team.

I’ll post an update here if there’s any news.

This topic was automatically closed 22 days after the last reply. New replies are no longer allowed.