Repository navigation
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.
Activity
Add provider-native Yolo mode for ACP sessions (#505)
aidangannon commentedon Sep 4, 2026
Can confirm this is still broken on 1.0.82 (macOS arm64,
@github/copilotvia Homebrew/npm), so it's not limited to the 1.0.81-x builds listed above.I wrote a minimal raw ACP client (init → session/new → session/prompt, same shape as the repro above) and confirmed
session/request_permissionis never sent for:touch/file creation and file edits inside the workspace (expected to be auto-approved per docs, so not surprising)~/Desktop/...), which percopilot help permissionsshould require confirmationcurlto a fresh domain not inallowedUrls)So it's not just "workspace trust" being over-broad — permission enforcement appears fully bypassed in
--acp --stdiomode regardless of path or URL scope, on the latest published version.As a workaround, running a cached pre-regression build directly (bypassing the
@github/copilotnpm wrapper, which always resolves to the latest installed native binary) restores correct behavior for me:This correctly sends
session/request_permissionand honors areject_onceresponse (tool call fails, file not created). Confirms the regression is isolated to the ACP permission-wiring path, same as OP's finding.patniko commentedon Oct 26, 2026
Thanks for the report. A fix that we believe addresses this has been merged and will ship in the next release (v1.0.104, currently in pre-release). Once it's out, could you retest and let us know whether you still see the problem? If we don't hear back after the release, we may close this issue — you can always reopen it.
Triaged as part of a Copilot SDK backlog review, with help from GitHub Copilot.
devm33 commentedon Oct 31, 2026
Verified resolved in released stable Copilot CLI v1.0.105.
Using the official Linux release binary and an isolated ACP client with the reported client capabilities, without allow-all launch flags, the agent sends
session/request_permissionfor the original workspacetouchcommand, an outside-workspace write, and a request to an unapproved URL. Bothreject_onceand the original client'scancelledoutcome prevent execution: no files are created and the refused URL receives no request.Closing as completed on that verification. Historical checks also passed on v1.0.88 and v1.0.89, so this confirms the released version contains the corrected behavior without attributing its first appearance to the previously suggested v1.0.104.
Generated by Copilot