github / github/copilot-cli

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

Đang mở
#4,537 1 bình luận 2 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

area:permissions
Ngôn ngữ chính
Shell
Star
11.2k
Fork
1.9k
Merge trung bình
14 giờ 16 phút
Pull request đã merge (30 ngày)
6

Mô tả

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.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với client acp-perm-repro.js và chạy nó trên các build được biết là tốt và bị ảnh hưởng bằng --acp cùng các thiết lập COPILOT_CLI_DIST_DIR đã được tài liệu hóa. Truy vết đường dẫn session/prompt của ACP quanh session/request_permission và so sánh với luồng cấp quyền của TUI; hoàn thành khi việc client từ chối ngăn lệnh shell chạy và các sự kiện cấp quyền được ghi lại như trong 1.0.81-0.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
javascript, node.js
Lĩnh vực
cli, security
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
52/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.