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: true alongside fs changes 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 / .completed |
recorded kind |
| TUI, user answers "Yes" |
logged |
approved |
| ACP client prompts, user allows once |
logged |
approved |
ACP client auto-answers allow_always |
logged |
approved-for-session |
| ACP 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.start carries 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 on leaves 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 +x and fourteen file edits — with zero permission.* events.
-
Note for anyone bisecting: the agent sends this request with "id": 0. A client testing if (m.id) will silently drop it and hang, which looks exactly like the bug. Worth ruling out before concluding a build is affected.
Describe the bug
In
--acpmode the agent no longer sendssession/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_DIRagainst builds cached under~/.copilot/pkg/linux-x64/:session/request_permission1.0.81-1 is named because it carries the only permission-related entry in the changelog between the last good and first bad build:
Steps to reproduce the behavior
Save as
acp-perm-repro.js(no dependencies), thenmkdir /tmp/acpdemo && node acp-perm-repro.js /tmp/acpdemo:On 1.0.81-0 (
COPILOT_AUTO_UPDATE=false COPILOT_CLI_DIST_DIR=~/.copilot/pkg/linux-x64/1.0.81-0):On 1.0.81-5:
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_permissionis a baseline method: the agent asks, the client decides. A client answeringcancelledshould 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:
permission.requested/.completedapprovedapprovedallow_alwaysapproved-for-sessiondenied-interactively-by-user--allow-all-tools(launch flag)--yolo(launch flag)/yolo on(runtime toggle)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.