ACP mode auto-approves tool calls again — session/request_permission not sent since 1.0.81-1 (regression of #845)
Nobody has claimed this yet.
- Dominant language
- Shell
- Stars
- 11.2k
- Forks
- 1.9k
- Avg merge
- 14h 16m
- Merged PRs (30d)
- 6
Description
Describe the bug
In --acp mode the agent no longer sends session/request_permission. Shell commands, file edits and deletions execute unattended: the client is given no opportunity to prompt, and nothing is written to the session log to say permission was ever waived.
This is the same defect as #845 (closed 2026-01-02). It returned in 1.0.81-1.
The permission engine itself is healthy — the interactive TUI on the same build still prompts for the identical command. Only the ACP path skips it.
Affected version
1.0.81-2, 1.0.81-3, 1.0.81-5 (current). Last good: 1.0.81-0.
Bisected with COPILOT_CLI_DIST_DIR against builds cached under ~/.copilot/pkg/linux-x64/:
| build | sends session/request_permission |
file created despite client refusing |
|---|---|---|
| 1.0.80 | yes | no |
| 1.0.81-0 | yes | no |
| 1.0.81-1 | not tested — not cached locally | |
| 1.0.81-2 | no | yes |
| 1.0.81-3 | no | yes |
| 1.0.81-5 | no | yes |
1.0.81-1 is named because it carries the only permission-related entry in the changelog between the last good and first bad build:
"Turning allow-all off from an ACP client now reaches the permission engine whenever there is a runtime override or auto-approval to revoke… (a baseline granted by
--allow-all-*launch flags is still deliberately left intact)" — PR 14283
Steps to reproduce the behavior
Save as acp-perm-repro.js (no dependencies), then mkdir /tmp/acpdemo && node acp-perm-repro.js /tmp/acpdemo:
// Minimal ACP client: initialize -> session/new -> session/prompt.
// Logs every request the agent sends us, and refuses any permission request.
const { spawn } = require('node:child_process');
const dir = process.argv[2] || process.cwd();
const say = (...a) => require('node:fs').writeSync(1, a.join(' ') + '\n');
const child = spawn('copilot', ['--acp'], { cwd: dir, stdio: ['pipe', 'pipe', 'ignore'] });
let id = 0, buf = '';
const pending = new Map();
const send = (o) => child.stdin.write(JSON.stringify(o) + '\n');
const request = (method, params) =>
new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); send({ jsonrpc: '2.0', id: i, method, params }); });
child.stdout.on('data', (d) => {
buf += d;
for (let n; (n = buf.indexOf('\n')) >= 0; ) {
const line = buf.slice(0, n); buf = buf.slice(n + 1);
let m; try { m = JSON.parse(line); } catch { continue; }
if (m.method && m.id !== undefined) { // agent -> client request
say('AGENT ASKS:', m.method);
send({ jsonrpc: '2.0', id: m.id,
result: m.method === 'session/request_permission'
? { outcome: { outcome: 'cancelled' } } // client refuses; tool must not run
: {} });
} else if (m.id !== undefined && pending.has(m.id)) {
const p = pending.get(m.id); pending.delete(m.id);
m.error ? p.rej(new Error(JSON.stringify(m.error))) : p.res(m.result);
}
}
});
(async () => {
const init = await request('initialize', {
protocolVersion: 1,
clientCapabilities: { fs: { readTextFile: true, writeTextFile: true }, terminal: false },
});
say('agent version:', init.agentInfo?.version);
const { sessionId } = await request('session/new', { cwd: dir, mcpServers: [] });
say('session:', sessionId);
await request('session/prompt', { sessionId, prompt: [{ type: 'text',
text: `Run exactly this shell command and nothing else: touch ${dir}/created-without-asking.txt` }] });
say('file exists:', require('node:fs').existsSync(`${dir}/created-without-asking.txt`));
child.kill();
})().catch((e) => { say(e.message); child.kill(); process.exit(1); });
On 1.0.81-0 (COPILOT_AUTO_UPDATE=false COPILOT_CLI_DIST_DIR=~/.copilot/pkg/linux-x64/1.0.81-0):
agent version: 1.0.81-0
AGENT ASKS: session/request_permission
file exists: false
On 1.0.81-5:
agent version: 1.0.81-5
file exists: true
The directory is fresh, has never been trusted, and none of these commands appear in ~/.copilot/permissions-config.json.
Expected behavior
As in #845 and the ACP specification, where session/request_permission is a baseline method: the agent asks, the client decides. A client answering cancelled should see the tool not run — which is what 1.0.81-0 does.
Additional context
-
The TUI is unaffected, which localises the fault. Driven under a pty on 1.0.81-5, the identical prompt produces "Do you want to run this command? …
touch …", and the file is not created. Only the ACP path skips the ask. -
Not gated on a client capability. Declaring
terminal: truealongsidefschanges nothing. -
Not explained by saved approvals or folder trust. Reproduced in a directory with neither.
-
Reproduced by two independent client implementations, one of them the ~30 lines above, written from scratch.
-
Nothing in the session log distinguishes this from a deliberate waiver. The log records permission decisions, not permission policy — so every path that reaches the engine is recorded however it is answered, and every path that bypasses the engine records nothing at all:
how the tool got permission permission.requested/.completedrecorded kind TUI, user answers "Yes" logged approvedACP client prompts, user allows once logged approvedACP client auto-answers allow_alwayslogged approved-for-sessionACP client refuses logged denied-interactively-by-user--allow-all-tools(launch flag)none — --yolo(launch flag)none — /yolo on(runtime toggle)none — 1.0.81-5 over ACP (this bug) none — session.startcarries no permission-posture field either (contextTier,reasoningEffort,copilotVersion… and nothing about approvals), and the affected build's log contains no string matching allow/approve/permission anywhere. So an ACP session that ran unsupervised because the agent stopped asking cannot be told apart afterwards from one where the operator knowingly disabled permissions — and/yolo onleaves no trace even though the TUI announces "All permissions are now enabled" on screen.A session here shows 105 tool calls in this state —
git checkout --detach,git commit,rm -rf,chmod +xand fourteen file edits — with zeropermission.*events. -
Note for anyone bisecting: the agent sends this request with
"id": 0. A client testingif (m.id)will silently drop it and hang, which looks exactly like the bug. Worth ruling out before concluding a build is affected.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the acp-perm-repro.js client and run it against the known good and affected builds using --acp and the documented COPILOT_CLI_DIST_DIR settings. Trace the ACP session/prompt path around session/request_permission and compare it with the TUI permission flow; done means a client refusal prevents the shell command and permission events are recorded as in 1.0.81-0.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100