Permissions are the difference between a fast autonomous agent and a fast autonomous disaster. Designing them well is the single highest-leverage config decision you'll make.
Think in tiers:
Read, Glob, Grep, reading filesBash(ls:*), Bash(cat:*), Bash(head:*), Bash(tail:*)Bash(git status), Bash(git log:*), Bash(git diff:*)mcp__search__*, mcp__docs__*Edit, Write, file modificationsBash(npm install:*), Bash(bun install:*), dependency installs (within trusted dirs)Bash(git add:*), Bash(git commit:*), local git workBash(git push:*), pushes to remoteBash(curl:*) with POST to unknown endpointsmcp__github__create_pr, mcp__stripe__chargeBash(sudo:*), noBash(rm -rf:*), noBash(security dump-keychain:*), keychain exfiltrationdelete_* patterns on production servicesBash(dd:*), mkfs:*, diskutil erase*, destructive disk opsAllow/deny entries use a specific pattern syntax:
Bash(ls), exactly ls with no argsBash(ls:*). ls with any argsmcp__notion__*, any tool from the notion MCPEdit, allow the built-in Edit tool (no pattern needed, it's a non-Bash tool)If a call matches both an allow pattern and a deny pattern, it's denied. Use this: broad allows + surgical denies.
"allow": ["Bash(git:*)"],
"deny": ["Bash(git push:*)", "Bash(git reset --hard:*)"]
Result: agent can do all git operations except push and hard-reset.
The goal of auto mode is "zero interruptions for 95% of work." Design it in two passes:
The /fewer-permission-prompts skill in Claude Code can analyze your recent session history and suggest allow-list additions.
Drop a .claude/settings.json in a project directory to override permissions for that project. Useful for prod repos (stricter) vs, playground repos (looser).
Claude Code records every tool call. Review periodically. If something surprising got auto-approved, tighten the allow list.