Skip to content
Agentic Control Plane

The GitHub Copilot control model, explained

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 honest summary is three short models rather than 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.

Escape hatch. --allow-all-tools approves every available tool; --allow-all (alias --yolo) adds all paths and all URLs. /allow-all and /yolo do the same mid-session, and /reset-allowed-tools undoes it. 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. None built in. GitHub’s own advice before using the permissive options is to sandbox the CLI yourself.

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 (bashBash, viewRead, createWrite) 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.”

Two open issues worth knowing 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 none 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.

Run agents? ACP lets you see, control, and price every tool call they make — allow/ask/deny policy, per-session cost, and a full audit log for Claude Code, Cursor, Codex, and OpenClaw, in one command:

macOS · Linux · WSL
curl -sf https://agenticcontrolplane.com/install.sh | bash
Windows · PowerShell
irm https://agenticcontrolplane.com/install.ps1 | iex

Getting started →  ·  see your first governed call →  ·  free up to 5 agents