Permissions are the difference between a fast autonomous agent and a fast autonomous disaster. Everything else about Claude Code matters, but this one page decides whether your agent is safe to run without watching. Get it right and the agent handles 95% of your work without interrupting. Get it wrong and you either get buried in approval prompts or you wake up one morning to find something got deleted. Let's go through how to design it well.
Every time Claude wants to do something in the real world, use a tool, run a Bash command, call an MCP, Claude Code checks your permission rules first. The rules can say three things:
That's it. Three verdicts. The art is writing rules that sort actions into those three buckets correctly for your work.
One thing to burn into memory: the deny list always wins. If a tool call matches both an allow pattern and a deny pattern, it's denied. This is what makes the system usable in practice: you can allow broad categories and deny narrow dangerous things inside them.
Before writing rules, it helps to classify actions into tiers by how much damage they can do. Every tool falls into one of these:
Anything that just looks at data. Reading a file, listing a directory, grepping for a string, checking git status. These are information-gathering tools. They can't do harm. Allow them without thinking.
Example patterns for your allow list:
Read, Glob, Grep, the built-in read toolsBash(ls:*), Bash(cat:*), Bash(head:*), Bash(tail:*)Bash(git status), Bash(git log:*), Bash(git diff:*)mcp__notion__search, mcp__github__read_*Things that modify your own machine: editing files, running npm install, making local commits. These can go wrong (e.g., a bad edit), but the damage stays on your machine and is usually trivial to reverse with git.
Example patterns:
Edit, Write, file modifications (within your project)Bash(npm install:*), Bash(bun install:*), dependency installsBash(git add:*), Bash(git commit:*), local git operationsBash(npm run:*), Bash(bun run:*), running project scriptsAnything that affects the outside world. Pushing to a remote, creating a PR, sending an email, charging a credit card, posting to Slack. Once these go, you can't undo them quietly. Always ask.
Leave these OFF your allow list entirely. Claude Code's default behavior (ask) is the right answer:
Bash(git push:*), pushes to remoteBash(curl -X POST:*), network POSTs to unknown endpointsmcp__github__create_pull_request, mcp__stripe__chargeThings you never want the agent to do under any circumstances, not even with your explicit approval. Put these in the deny list so they don't even show up as prompts.
Canonical deny patterns:
Bash(sudo:*), elevating to root, just noBash(rm -rf:*), recursive deleteBash(dd:*), Bash(mkfs:*), Bash(diskutil erase*), disk destructionBash(security dump-keychain:*), credential exfiltrationmcp__*__delete_* on production servicesgit reset --hard, git push --force, or git clean -fRules use a compact pattern language:
Bash(ls), exactly the literal command ls with no argumentsBash(ls:*), ls with any argumentsBash(npm:*), any npm subcommandmcp__notion__*, any tool from the Notion MCP serverEdit, the built-in Edit tool (no Bash wrapping, no args)Bash(git push origin main), an exact push command (deny this specifically)Glob-style; no regex. Simple enough to reason about, expressive enough to cover what you need.
The most maintainable pattern is to allow broad categories of action and then cut out the dangerous narrow cases. Example:
"allow": [
"Read", "Glob", "Grep",
"Edit", "Write",
"Bash(git:*)",
"Bash(npm:*)", "Bash(bun:*)",
"mcp__notion__*",
"mcp__github__read_*"
],
"deny": [
"Bash(sudo:*)",
"Bash(rm -rf:*)",
"Bash(git push:*)",
"Bash(git reset --hard:*)",
"Bash(git push --force:*)",
"mcp__github__delete_*"
]
Reading the list like a person: "You can do git stuff, npm stuff, read/edit files, and everything in Notion. But you can't sudo, can't rm -rf, can't push, can't force-push, can't hard-reset, and can't delete things on GitHub." That's usually exactly the right balance for a developer workflow.
Don't try to design the perfect list upfront. Build it the way a veteran sysadmin builds firewall rules: tight at first, then loosen where you're hitting friction.
Claude Code ships a /fewer-permission-prompts skill that scans your session history and suggests patterns to add. It's the fastest way to tune a list from good to excellent.
Drop a .claude/settings.json inside any project directory. Claude Code picks it up automatically when you run claude from that project. This lets you have:
production/")You can also commit these configs into git so the whole team inherits the same guardrails.
Every tool call Claude Code runs goes into an audit log. Periodically, skim it. If you see something surprising, "oh, the agent ran mcp__stripe__list_charges, I didn't know it could do that", tighten the allow list. The audit log is how you find rules you should have denied and didn't.
Andrej Karpathy - Let's build GPT from scratch