The GitHub Copilot control model, explained
In Copilot CLI, read-only tools run freely and anything that modifies the system prompts, with --allow-tool and --deny-tool setting session rules and deny always winning, even under --allow-all. In VS Code agent mode, a per-session permission level and chat.tools.terminal.autoApprove decide what runs without asking, and only a PreToolUse hook can actually block a command.
GitHub Copilot is three agents under one name: Copilot CLI in the terminal, agent mode in VS Code, and the cloud coding agent that works a GitHub issue into a pull request. They share an account and a subscription, and since 2026 they share a hook system, but their controls differ enough that the accurate summary is three short models, not one.
The reason this page matters more than its size suggests: Copilot is the harness most teams already have, because it came with the GitHub plan. When a team says it runs a mix of agents, Copilot is usually the one nobody chose.
This page covers Copilot’s own controls. For adding ACP to Copilot, see the install guide; for the cross-harness picture, the controls comparison.
Copilot CLI
Approvals. Read-only tools (search, file reads, read-only shell) run without asking. Anything that can modify the system prompts: destructive shell commands, file edits, URL access. At the prompt you can allow the tool once, for the rest of the session, or in some cases “don’t ask again in this repo”, which is written to ~/.copilot/permissions-config.json scoped to the git root. Permanent URL approvals go to allowedUrls in ~/.copilot/settings.json.
Rules. Two layers on the command line. --available-tools and --excluded-tools decide which tools exist at all; then --allow-tool and --deny-tool decide what runs without a prompt, with a grammar that reaches into shell subcommands:
copilot --allow-tool='shell(git:*)' --deny-tool='shell(git push)'
copilot --deny-tool=write
copilot --allow-tool='read, write(.github/copilot-instructions.md)'
copilot --allow-tool='MyMCP(create_issue), MyMCP(delete_issue)'
These flags are session-only and are not saved. Deny wins unconditionally: GitHub’s docs state that deny rules take precedence over allow rules “even when --allow-all is set”, and over saved approvals.
Permission modes. /permissions switches the session between modes: default (the manual prompts above), assisted and allow-all, and /permissions show prints the current one (CLI command reference). In assisted mode, per GitHub’s docs, “a large language model assesses eligible permission requests and automatically approves those it judges to be safe”; existing permissions and enterprise rules still apply, and anything it does not approve comes back to you. For non-interactive runs (-p), --assisted-approval (env COPILOT_ASSISTED_APPROVAL) turns it on; it takes precedence over --allow-all-tools but does not affect --allow-all-paths or --allow-all-urls.
Escape hatch. --allow-all-tools approves every available tool; --allow-all (alias --yolo) adds all paths and all URLs. /allow-all and /yolo are aliases for /permissions allow-all mid-session, and /reset-allowed-tools returns the mode to manual. On Copilot Business and Enterprise, an administrator can block the permissive options. The CLI also ships /fleet, which fans one prompt across several agents, and an autopilot that auto-responds to requests that would otherwise wait for you; how the allow/deny rules apply to fleet-spawned subagents is not documented.
Sandbox. Now built in. GitHub documents local sandboxing, which restricts “access to your filesystem, network, and system capabilities”, and cloud sandboxing, where copilot --cloud runs the whole session in a GitHub-hosted environment. Locally, /sandbox enable and /sandbox disable change your saved setting, /sandbox enable session changes only the current session, /sandbox status and /sandbox policy show what is in force, and --sandbox turns the OS-level shell sandbox on for one run without touching the saved setting (CLI command reference). An enterprise-managed policy can enforce sandboxing: /sandbox disable is then refused unless bypass is allowed, and --no-sandbox is ignored. Earlier releases had none, so older advice to sandbox the CLI yourself before using the permissive options predates this. Commands and limits are in the Copilot CLI permissions and hooks reference.
VS Code agent mode
Approvals. A permission level per chat session, defaulting to chat.permissions.default:
| Level | What runs without asking |
|---|---|
| Manual (default) | Whatever your tool, URL and terminal auto-approve settings already cover. Everything else prompts |
| Assisted (experimental) | Whatever an LLM judge approves. Requires chat.assistedPermissions.enabled; organizations can hide it |
| Allow all | Everything: file edits, terminal commands, external tool calls |
Autopilot is a mode rather than a level: it approves everything Allow all does, retries on errors, and answers the agent’s own blocking questions. chat.tools.global.autoApprove removes prompts in every workspace, with a warning dialog the first time; the docs recommend the per-session level instead.
Rules. chat.tools.terminal.autoApprove is a map of command names or /regex/ patterns to true (run) or false (prompt). Compound commands are matched per subcommand, and false always wins. The defaults auto-approve common read-only commands and prompt on rm and del. The sentence to remember is VS Code’s own: a false rule “only forces a prompt; it does not block. To block, use a PreToolUse hook returning permissionDecision: "deny".” And: “Terminal auto-approval is a best-effort convenience, not a security boundary.” The parser is tree-sitter, zsh and fish are read as bash, and obfuscation is acknowledged as a limit. Per-tool, chat.tools.eligibleForAutoApproval can pin any tool to manual approval.
Sandbox. Off by default on every platform. chat.agent.sandbox.enabled turns it on for macOS, Linux and WSL2 (bubblewrap and socat required on Linux); chat.agent.sandbox.enabledWindows for Windows, experimental and dependent on the September 2026 security update. It bounds terminal commands and their child processes only, not the file tools; writes are limited to the working directory; the network is open unless chat.agent.sandbox.allowNetwork is set to false. It stays in force under Allow all and Autopilot, and sandboxed commands are auto-approved by default. Organizations can require it through managed settings.
Hooks, in both
Copilot’s hook system is the layer that sees every tool call with its arguments, across every approval level, and the one place a call can be blocked rather than merely prompted. Eight events in VS Code (SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PreCompact, SubagentStart, SubagentStop, Stop); Copilot CLI adds permissionRequest, errorOccurred and notification. PreToolUse returns permissionDecision of allow, ask or deny, with a reason the agent sees on a deny.
| Copilot CLI | VS Code agent mode | |
|---|---|---|
| Locations | Admin policy files (/etc/github-copilot/policy.d/*.json, C:\ProgramData\GitHub\Copilot\policy.d\*.json), repo .github/hooks/*.json, user ~/.copilot/hooks/*.json, repo .claude/settings.json and .claude/settings.local.json, plugins |
Repo .github/hooks/*.json, repo .claude/settings.json and .claude/settings.local.json, user ~/.copilot/hooks, user ~/.claude/settings.json, plugins |
| Payload | Two formats, chosen by event-name casing: camelCase events carry toolName / toolArgs; PascalCase events carry snake_case fields with tool_name in Claude Code’s vocabulary (bash → Bash, view → Read, create → Write) |
Claude Code’s field names, with VS Code’s own tool ids (run_in_terminal, create_file, replace_string_in_file) and camelCase inputs |
| Decision | permissionDecision at the top level; modifiedArgs rewrites arguments |
hookSpecificOutput.permissionDecision; updatedInput rewrites arguments, ignored if it fails the tool’s schema |
| Matchers | Regex on the tool name, anchored | Ignored. Every hook runs on every tool |
| Exit codes | 2 denies, even if stdout said allow. Any other crash of a preToolUse command hook fails closed. Timeouts (default 30 s) fail open, “including preToolUse and admin-deployed policy hooks” |
0 parses stdout, 2 is a blocking error with stderr shown to the model, other codes warn and continue |
| Off switch | disableAllHooks in a hooks file or the repo settings; policy hooks still run |
Organization policy can disable hooks |
| Status | GA | Preview |
Three consequences. First, Copilot reads Claude Code’s format natively, so a repository that commits .claude/settings.json hooks for Claude Code already governs VS Code agent mode and Copilot CLI in that repo. Second, VS Code ignores matchers, so a hook must dispatch on tool_name itself. Third, the admin policy.d path gives an enterprise a way to deploy a hook to every CLI on a managed machine, and the timeout rule means that deployed hook still fails open when it is slow. GitHub’s stated reason is the right one: “a slow or unreachable hook must not silently block tool calls or work.”
Check two open issues before trusting a deny in a given version: a Copilot CLI release in which a preToolUse deny was not honoured (copilot-cli#3874), and plugin-bundled preToolUse hooks not firing (copilot-cli#2540). Repo and user hooks are the paths without a filed regression.
The cloud coding agent
The coding agent runs in GitHub Actions and reads only .github/hooks/*.json. preToolUse fires; permissionRequest and notification do not; only bash entries run. GitHub documents that an ask is treated as a deny there. That is the correct posture and it is written down, which is rarer than it should be. What the sandbox lacks is any credential a hook could use to report home, so a hook that governs by consulting a policy service passes cloud runs through unless the repository provisions one.
The empty chair
Three answers, one per agent. The cloud coding agent resolves an ask to deny, natively. VS Code under Autopilot answers its own questions, which is the point of Autopilot and the reason the sandbox stays on underneath it. Copilot CLI in a non-interactive run is not documented either way; the permissionRequest hook is offered as the tool for that case, which means the answer is whatever your hook says.
The gap is the same as everywhere else. Neither the CLI nor VS Code documents a durable per-tool-call record of what was allowed, prompted or denied. The decision happens and is gone. A hook that reports each decision to an independent record closes it, and Copilot’s hooks are built to carry exactly that.
Where Copilot sits
Against the comparison table: a full native permission model in the CLI with an unusually good deny grammar and deny-beats-allow-always, a VS Code model whose rules prompt rather than block by its own admission, an off-by-default sandbox in VS Code and an opt-in one in the CLI, a Claude-contract hook surface in both with one genuine enterprise pin (policy.d), and a documented headless posture for the cloud agent. The thing to know before trusting the defaults is that VS Code’s terminal rules are a convenience layer, and its own docs point at hooks for the boundary.
Frequently asked questions
How do permissions work in Copilot CLI?
Read-only tools run without asking. Anything that can modify the system — shell commands, file writes, URL access — prompts, and you can allow once, for the session, or ‘don’t ask again in this repo’, which is saved to ~/.copilot/permissions-config.json. Session-only rules come from –allow-tool and –deny-tool with a shell(git:*) grammar. Deny rules always beat allow rules, even under –allow-all.
Does VS Code's terminal auto-approve block commands?
No. A false rule in chat.tools.terminal.autoApprove forces a prompt; it does not block. VS Code’s own docs say that to block a command you need a PreToolUse hook that returns permissionDecision deny, and that terminal auto-approval is a best-effort convenience, not a security boundary.
Do Copilot hooks work like Claude Code hooks?
Copilot reads Claude Code’s hook format natively. Both Copilot CLI and VS Code load .claude/settings.json from the repository, and PascalCase event names select a payload with snake_case fields and Claude’s tool names. The decision is permissionDecision allow, ask or deny; exit code 2 denies. Two differences: VS Code ignores matchers and runs every hook on every tool, and Copilot CLI’s preToolUse fails closed on a crash but open on a timeout.
What happens to an ask when the Copilot coding agent runs in the cloud?
It becomes a deny. GitHub documents that the cloud coding agent runs preToolUse hooks from .github/hooks/ and treats a permissionDecision of ask as deny. There is no one to ask, so the request is refused rather than waved through.
What does copilot --allow-all do?
--allow-all-tools approves every available tool, and --allow-all (alias --yolo) also adds all paths and all URLs. /allow-all and /yolo do the same mid-session and /reset-allowed-tools undoes it. Deny rules still take precedence, even with --allow-all set.
Does Copilot CLI have a sandbox?
Yes, since late 2026. GitHub documents first-party local sandboxing (/sandbox enable, or --sandbox for one session) and cloud sandboxing (copilot --cloud), and organizations can require it. Earlier releases had none, which is why older guides say to sandbox the CLI yourself. VS Code agent mode has its own, off by default: chat.agent.sandbox.enabled turns it on for macOS, Linux and WSL2, and it bounds terminal commands only, not the file tools. Details in the Copilot CLI permissions and hooks reference.
Running coding agents? ACP records each tool call its hook sees and makes the dangerous shell commands (a force-push, destructive SQL, curl | sh) wait for you. One command, free up to 5 agents:
curl -fsSL https://agenticcontrolplane.com/install.sh | bash
irm https://agenticcontrolplane.com/install.ps1 | iex