Summary:
In ACP mode, approvalMode: "auto-review" does not apply its auto-review behavior. The AI classifier that auto-runs safe/trusted calls does not run, the autoRun.allow_instructions / block_instructions are never consulted, and .cursor/permissions.json (which holds autoRun, terminalAllowlist, mcpAllowlist) is not read at all. Only the static permissions.allow tokens in cli.json/cli-config.json take effect — so auto-review behaves identically to allowlist under ACP.
Environment:
cursor-agentversion2026.07.09-a3815c0- macOS (Darwin),
cursor-agent acp,approvalMode: "auto-review" - ACP session mode
agent, modelgrok-4.5
Repro:
- Global
~/.cursor/cli-config.jsonwithapprovalMode: "auto-review". - Drive
cursor-agent acp(client below) and prompt it to run read-only commands:pwd,cat package.json,date. - Result: all three trigger
session/request_permission— even though auto-review’s classifier should auto-run non-mutating inspections. - Add a project
.cursor/permissions.jsonwithterminalAllowlist: ["pwd","cat","date"]andautoRun.allow_instructions: ["Allow read-only commands …"]. Re-run. - Result: still prompts for all three — no change.
- Discriminating test: put a bogus key in
.cursor/permissions.json→ ACP server starts with no error (file not validated/read). Put a bogus key in.cursor/cli.json→ ACP server rejects it with a schema validation error at startup (this file is read).
Captured IDs (live runs, one session per command, all prompted under auto-review):
[pwd]
sessionId: 6c34e8fe-06ce-4a16-b74e-41ca80c57ad3
request id: 0
toolCallId: call-8cb3b224-920f-4fa0-bff5-33f3f36e8e56-0
fc_c31a79fd-d95d-9c4e-a2de-9cdb3e66964f_0
kind/title: execute / `pwd`
[cat package.json]
sessionId: a1eca519-9be3-4fe2-9b54-46834a381fcd
request id: 0
toolCallId: call-c32e79a0-d05a-4076-8ab7-45dda8733556-0
fc_04de762f-36cc-9a9a-8de4-f61684c40133_0
kind/title: execute / `cat package.json`
[date]
sessionId: 3925867d-4d53-41ae-9983-beb85ca6fb81
request id: 0
toolCallId: call-924e354d-03d0-411e-ac51-d956eff9da28-0
fc_9357da0c-d646-983a-98be-0f80c75949d7_0
kind/title: execute / `date`
Expected: Under auto-review, the classifier and autoRun instructions should apply in ACP (read-only commands auto-run; autoRun/terminalAllowlist from permissions.json honored).
Actual: Auto-review’s distinctive behavior is entirely absent in ACP; .cursor/permissions.json is ignored; only permissions.allow static tokens in cli.json/cli-config.json and approvalMode: unrestricted have any effect.
Impact: Users who configure auto-review + autoRun rules (the documented recommended setup) get a full manual-approval experience through any ACP client, with no indication their config is being ignored.
Suggested fix: Honor approvalMode: auto-review (classifier + autoRun) in ACP mode, and read .cursor/permissions.json in ACP — or, at minimum, document that ACP only consults cli.json/cli-config.json permissions.allow + approvalMode, and surface a warning when a permissions.json is present but ignored.
Attachment — minimal ACP repro client (acp-repro.mjs)
Run with node acp-repro.mjs <cwd> "<prompt>". It auto-approves any permission request (so the run completes) and logs the sessionId + every session/request_permission with its ids. For this ticket, run with prompts such as "Run exactly this shell command and nothing else: pwd".
import { spawn } from "node:child_process";
const cwd = process.argv[2] ?? process.cwd();
const text = process.argv[3] ?? "Run exactly this shell command and nothing else: pwd";
const child = spawn("cursor-agent", ["acp"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
let buf = "";
let id = 0;
const pending = new Map();
const send = (method, params) => {
const msg = { jsonrpc: "2.0", id: ++id, method, params };
child.stdin.write(JSON.stringify(msg) + "\n");
return new Promise((r) => pending.set(msg.id, r));
};
const respond = (rid, result) =>
child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: rid, result }) + "\n");
child.stdout.on("data", (d) => {
buf += d.toString();
let i;
while ((i = buf.indexOf("\n")) >= 0) {
const line = buf.slice(0, i);
buf = buf.slice(i + 1);
if (!line.trim()) continue;
let m;
try { m = JSON.parse(line); } catch { continue; }
if (m.method === "session/request_permission") {
const tc = m.params.toolCall ?? {};
console.log(
`PERMISSION_REQUEST jsonrpc.id=${m.id} toolCallId=${tc.toolCallId} ` +
`kind=${tc.kind} title=${JSON.stringify(tc.title)}`,
);
// auto-approve so the turn completes
respond(m.id, { outcome: { outcome: "selected", optionId: m.params.options?.[0]?.optionId } });
} else if (m.id && pending.has(m.id)) {
pending.get(m.id)(m);
pending.delete(m.id);
}
}
});
child.stderr.on("data", (d) => process.stderr.write("[stderr] " + d));
await send("initialize", {
protocolVersion: 1,
clientCapabilities: { fs: { readTextFile: true, writeTextFile: true } },
});
const sess = await send("session/new", { cwd, mcpServers: [] });
console.log(
`sessionId=${sess.result.sessionId} currentMode=${sess.result.modes?.currentModeId} ` +
`model=${sess.result.models?.currentModelId}`,
);
await send("session/prompt", { sessionId: sess.result.sessionId, prompt: [{ type: "text", text }] });
child.kill();
process.exit(0);