GitHub Copilot CLI permissions: --allow-tool, --deny-tool and the patterns behind them

Copilot CLI asks before it runs commands or writes files, unless you tell it not to. The flags that tell it are short and easy to get wrong. Here is how the patterns match, what wins, and where approvals persist.

The short answer

GitHub Copilot CLI’s --allow-tool lets a tool run without a prompt, and --deny-tool blocks it outright. Both take patterns such as shell(git:*), shell(git push), write(src/*.ts) or MyMCP(create_issue).2 Deny rules always take precedence over allow rules, even when --allow-all is set or a matching approval was saved earlier.1 So the classic setup, “let it use git but never push”, is:

Code Commands typed in a terminal. The text around it says what happens.

copilot --allow-tool='shell(git:*)' --deny-tool='shell(git push)'

Two cautions up front. Command-line rules apply to the current session only, while approvals you click “always” on are saved per repository in ~/.copilot/permissions-config.json.1 And a shell rule matches the command you see; GitHub’s own docs note that a shell command can do anything your user account can.1

Every MCP tool call goes through your rules first. Here a delete that doesn’t say which rows is stopped before it reaches the database. These are the rules deedglass init starts with; the call is made up.

Two layers of control

Copilot CLI separates what the model can see from what it may run without asking.1

Layer Flags Effect
Visibility --available-tools Allowlist: tools not listed are hidden from the model
Visibility --excluded-tools Denylist: hides only the named tools
Permission --allow-tool Runs without prompting
Permission --deny-tool Blocked, never prompted

If you pass both visibility flags, the allowlist applies and the denylist is ignored, and a tool outside the available set can’t be used even with --allow-tool.1

The visibility layer is underrated. GitHub’s docs point out that without it, the model may keep attempting tools that are then denied, wasting turns.1 Hiding a tool is cleaner than denying it: the model plans without it.

The pattern syntax

From the command reference, every pattern is Kind(argument), and leaving the argument out matches all tools of that kind.2

Kind Covers Examples
shell Shell commands shell, shell(git push), shell(git:*)
write Creating or changing files write, write(src/*.ts)
read File and directory reads read, read(.env)
url URL access via web fetch or shell url(github.com), url(https://*.api.com)
memory Storing facts to agent memory memory
MCP server name That server’s tools MyMCP, MyMCP(create_issue)

Three matching rules worth knowing:2

  • :* means “this command, then a space”. shell(git:*) matches git push and git pull but not gitea. Without it you’d be matching a prefix.
  • Path denials are narrow. --deny-tool='write(secret.txt)' blocks that path only, by exact or trailing-path-segment match, with no glob support yet. The match resolves symlinks and . and .., and is case-insensitive on macOS and Windows.
  • Built-in tools aren’t shell commands. A public issue shows a headless run passing shell(glob:*), shell(grep:*) and shell(view:*) and still getting “Permission denied and could not request permission from user” for the glob, grep and view tools.3 Those are separate tools; file reads fall under the read kind. If a headless run is denied, read the tool name in the error and map it to the right kind.

What deny wins over

Deny is the one control that holds across modes. It beats:1

  • any --allow-tool rule,
  • --allow-all and --yolo,
  • approvals saved in permissions-config.json.

Enterprise restrictions also still apply under allow-all.4 That makes --deny-tool the right home for the handful of commands you never want run unattended: shell(git push), shell(rm:*), shell(terraform:*), shell(kubectl:*), your database CLIs.

But deny matches command text. shell(rm:*) won’t stop a script that deletes files, find . -delete, or a Python one-liner. Treat deny rules as a guard against the obvious, not a sandbox.

Where approvals are saved

When Copilot asks, the interactive prompt offers to allow once, to allow that tool for the rest of the running session, or to say no and tell Copilot what to do differently.4 GitHub’s docs give the warning case: approving rm for the session lets Copilot delete files in the current directory and below without asking.4

Approvals tied to a location, covering shell, file writes, MCP tools, memory and directories, are written to ~/.copilot/permissions-config.json. Location means the Git repository root, or the working directory outside a repo. URL approvals go to allowedUrls in ~/.copilot/settings.json and apply across all sessions.1

/reset-allowed-tools revokes session grants, returns the mode to manual, and clears saved approvals for the current location, while your startup --allow-tool and --deny-tool flags stay as the baseline. Other locations’ approvals stay until you edit or delete them in the file.12 Review that file now and then; it’s easy to forget an “always” from months ago.

The allow-all options

Option Grants
--allow-all-tools Every available tool without prompts or a model safety review; deny rules and enterprise restrictions still apply2
--allow-all / --yolo Tools, plus all paths and all URLs2
COPILOT_ALLOW_ALL Same as --allow-all; set to exactly true, it also trusts the working directory and loads its skills, plugins, MCP servers and hooks2
/permissions assisted A model reviews eligible requests and auto-approves those it judges safe; others still come to you4

GitHub’s guidance is blunt: use the allow-all options only in an isolated environment, and never put them in a shell alias, because that applies them on every launch.1 The COPILOT_ALLOW_ALL=true detail deserves attention in CI: a cloned repository’s hooks and MCP servers load without a trust prompt.2

Organisations can switch these off. With the managed setting permissions.disableBypassPermissionsMode set to disable, the allow-all flags and the /yolo and /allow-all commands are suppressed; assisted permissions are controlled separately.2

Safe defaults for three situations

Everyday interactive use. No flags beyond a deny list. Answer prompts once, not “for the session”, for anything that deletes or pushes.

Code Commands typed in a terminal. The text around it says what happens.

copilot --deny-tool='shell(git push), shell(rm:*)'

A scripted run that may commit but not push. This is the combined example from GitHub’s docs:1

Code Commands typed in a terminal. The text around it says what happens.

copilot -p "Fix the failing unit test and commit" \
  --available-tools='bash,edit,view,grep,glob' \
  --allow-tool='shell(git:*)' --deny-tool='shell(git push)'

CI with broad permissions. Only inside a disposable runner or container, with no production credentials in the environment, and with /sandbox enable or a cloud session where available.4 Keep the deny list anyway, since it still applies.1

What these flags can’t do

  • They don’t see effects. shell(npm:*) allows npm run migrate, whatever the migration does.
  • They don’t follow scripts. Allowing shell(./deploy.sh) allows everything inside it.
  • Credentials decide the blast radius. If the shell holds a production database URL, any allowed command can use it. The fix is a narrower credential, not a longer deny list.
  • Hooks are a second layer. Copilot’s preToolUse hooks can inspect arguments before a tool runs; our post on Copilot’s preToolUse hooks covers how they fail.

For a side-by-side with other agents, see hooks in Codex, Gemini, Copilot and Cursor and Claude Code’s deny, ask and allow rules.

Questions people ask

How do I let Copilot CLI use git but not push?

--allow-tool='shell(git:*)' --deny-tool='shell(git push)'. The deny wins.2

Does –deny-tool still apply with –yolo?

Yes. Deny rules take precedence even with --allow-all, which --yolo is equivalent to.12

Why does my headless run say permission denied for grep or glob?

Those are built-in tools, not shell commands, so shell(grep:*) doesn’t cover them.3 Use the permission kind that fits the tool (file reads are the read kind) instead of a shell pattern.2

How do I undo an “always allow”?

Run /reset-allowed-tools in that repository, or edit ~/.copilot/permissions-config.json.1

To see which coding agents, MCP servers and credentials on your machine can reach a database, run npx deedglass scan (the grade, explained). To try approvals on a real database in your browser, open the playground.

Written with AI assistance and checked against our claims register.

The takeaway

In Copilot CLI a deny rule beats every allow, even --yolo and saved approvals; but shell rules match command text, and saved approvals persist per repository.

Read nextVS Code agent mode auto-approve: which terminal commands run without asking Try it: install in 7 s

Related on this site

More posts

Sources

  1. 1GitHub docs: Allowing and denying tool use (Copilot CLI) · docs.github.com · checked 27 October 2026
  2. 2GitHub docs: Copilot CLI command reference · docs.github.com · checked 27 October 2026
  3. 3github/copilot-cli #1482: how to map tool names to --allow-tool patterns · github.com · checked 27 October 2026
  4. 4GitHub docs: Using GitHub Copilot CLI · docs.github.com · checked 27 October 2026

Published 27 October 2026, sources last checked 27 October 2026. If something here is out of date, write to hello@deedglass.com.

Try it on your own setup

npx deedglass scan

Grades only your own setup. Nothing is uploaded.

Pricing