github / github/copilot-cli

ACP mode auto-approves tool calls again — session/request_permission not sent since 1.0.81-1 (regression of #845)

Ouverte
#4,537 1 commentaire 2 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

area:permissions
Langage dominant
Shell
Étoiles
11.2k
Forks
1.9k
Merge moyen
14 h 16 min
PR mergées (30 j)
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: 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.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez avec le client acp-perm-repro.js et exécutez-le sur les builds connue comme fonctionnelle et affectée en utilisant --acp et les paramètres documentés de COPILOT_CLI_DIST_DIR. Suivez le chemin de session/prompt ACP autour de session/request_permission et comparez-le au flux d’autorisation de la TUI ; le travail est terminé lorsqu’un refus du client empêche la commande shell et que les événements d’autorisation sont enregistrés comme dans 1.0.81-0.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
javascript, node.js
Domaine
cli, security
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
Active
Clarté
Plutôt claire
Accessibilité débutants
52/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.