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
Deedglass
A tool call meets your rules before it runs
Asks to run
DELETE FROM visits;
Deletes every row in visits
Deedglass checks your rules
- Reading dataAllowed
- Deleting a whole table (DROP, TRUNCATE)Never
- Changing or deleting rows without saying which (WHERE)This one
- Other changesWait for a person
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:*)matchesgit pushandgit pullbut notgitea. 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:*)andshell(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 thereadkind. 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-toolrule, --allow-alland--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:*)allowsnpm 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
preToolUsehooks 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.