Claude Code does not have a flat permission model. Before it runs any tool — bash commands, file edits, web fetches — it evaluates a layered set of trust signals to decide whether to ask for confirmation, proceed automatically, or refuse outright.
Understanding this system means fewer interruptions on safe operations and fewer surprises on dangerous ones.
The Four Permission Layers
Claude Code checks permissions in this order:
- Operator system prompt — Injected by the environment running Claude Code. In enterprise deployments, this locks down capabilities before the user ever interacts.
- User-level settings —
~/.claude/settings.json. Applies across all projects on the machine. - Project-level settings —
.claude/settings.jsoninside the repo. Checked into version control and shared with the team. - Session-level overrides — Granted interactively during the session. Not persisted.
The first layer that explicitly denies an operation wins. If a layer is silent, evaluation falls through to the next.
Tool Categories and What Requires Permission
Claude Code groups its tools into categories. The default permission requirement for each:
| Tool Category | Examples | Default |
|---|---|---|
| Read-only file ops | Read, Glob, Grep | Allowed |
| Write file ops | Write, Edit, MultiEdit | Allowed (with diffs shown) |
| Bash commands | Any shell execution | Ask |
| Web tools | WebSearch, WebFetch | Ask |
| MCP tools | Depends on server | Depends on server |
The “Ask” default means Claude Code will display the command and wait for your confirmation before running. This is intentional friction for anything that touches external systems or has side effects outside the current repo.
Configuring Allowed Commands in settings.json
The most common customization is whitelisting safe bash commands so Claude Code doesn’t ask for confirmation repeatedly.
Project-level (.claude/settings.json):
{
"permissions": {
"allow": [
"Bash(npm run test)",
"Bash(npm run lint)",
"Bash(npm run build)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(git push --force *)"
]
}
}
User-level (~/.claude/settings.json):
{
"permissions": {
"allow": [
"Bash(ls *)",
"Bash(cat *)",
"Bash(echo *)",
"WebSearch(*)"
]
}
}
The glob patterns in allow and deny match against the full command string. Bash(git *) covers any git subcommand. Bash(npm run *) covers any npm script. Denial rules override allow rules at the same level.
Dangerous Operations and Hard Limits
Some operations have elevated scrutiny regardless of your allow list:
Always requires confirmation:
- Commands that contain
sudo - Commands that pipe to
shorbash(e.g.,curl | bash) - Writing outside the current working directory
- Deleting files (any
rmthat isn’t obviously safe)
Never allowed regardless of settings:
- Exfiltrating secrets or credentials
- Operations explicitly blocked by the operator system prompt
- Tool calls not in Claude Code’s known tool set
These limits exist at the model level, not just the config level. Adding Bash(sudo *) to your allow list will not cause Claude Code to run sudo commands without asking — the model will still confirm before executing anything that looks like privilege escalation.
Trust Level: How Claude Code Reads Context
Beyond explicit settings, Claude Code infers a “trust level” from the environment:
High trust signals:
- You are in a directory with a
.gitfolder (it is your project) - You have been confirming commands consistently in this session
- The operation matches patterns from the project’s CLAUDE.md
Lower trust signals:
- You are in a temporary directory or
/tmp - The command interacts with remote systems (SSH, curl, APIs)
- The file path goes outside the project root
This is heuristic, not deterministic. The trust level affects how Claude Code frames its confirmation dialogs — high trust gets shorter prompts with less explanation, lower trust gets longer warnings.
Setting Permissions in CLAUDE.md
You can describe permission policies in CLAUDE.md as natural language. These are instructions to Claude, not to the settings system, but they affect behavior:
## Permissions
- Run `npm run test`, `npm run lint`, and `npm run build` without asking
- Always ask before any `git push` or `git commit`
- Never run `rm -rf` — use `trash-cli` instead if deletion is needed
- Ask before any network request outside localhost
This is softer than JSON settings — Claude Code will generally follow these instructions, but they can be overridden in conversation. For hard limits, use settings.json.
MCP Server Permissions
MCP servers declare their own permission surface. When you connect an MCP server, Claude Code shows what tool categories it exposes. You can restrict which MCP tools are available at the project level:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/safe-dir"]
}
},
"permissions": {
"deny": [
"mcp__filesystem__write_file",
"mcp__filesystem__delete_file"
]
}
}
This gives the MCP server read access to /Users/me/safe-dir while blocking any write operations at the Claude Code permission layer. The MCP server may also have its own internal access controls — these are additive.
The Practical Setup for Teams
A typical team setup uses three layers:
~/.claude/settings.json (developer machine defaults):
{
"permissions": {
"allow": [
"Bash(ls *)", "Bash(echo *)", "Bash(cat *)",
"Bash(git status)", "Bash(git diff *)", "Bash(git log *)"
]
}
}
.claude/settings.json (project-specific, committed):
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(docker compose ps)",
"Bash(docker compose logs *)"
],
"deny": [
"Bash(docker compose down --volumes)",
"Bash(git push --force *)"
]
}
}
CLAUDE.md (soft behavioral guidance):
## Tool Use Policy
- Prefer `npm run test -- --watch=false` for CI-compatible test runs
- Use `docker compose ps` to check container state before any migration
- Never drop production databases. Staging is OK with explicit confirmation.
This combination means: global safe commands are always allowed, project-specific scripts don’t need confirmation, and a small set of dangerous operations are hard-blocked by policy. CLAUDE.md handles the nuanced cases that don’t map cleanly to glob patterns.
Auditing What’s Allowed
To see the effective permission set for a project, run:
cat .claude/settings.json
cat ~/.claude/settings.json
There is no single merged view — you need to read both files and mentally combine them. The deny list at project level can override allows at user level. The reverse is not true (user-level denies cannot be overridden by project-level allows).
This means if your team’s project-level settings deny git push --force, individual developers cannot re-enable it in their ~/.claude/settings.json.
Summary
The permission system gives you three tools:
- JSON allow/deny lists for deterministic, version-controlled policy
- CLAUDE.md instructions for nuanced behavioral guidance that doesn’t fit patterns
- Operator system prompts for enterprise-level locked-down environments
For most teams, the right answer is project-level settings.json for the hard rules (committed and enforced), CLAUDE.md for the soft rules (advisory, flexible), and user-level settings for personal productivity shortcuts that don’t affect team policy.