Claude Code permissions: --dangerously-skip-permissions vs allowedTools explained
Every Claude Code user hits this moment: you're mid-session, Claude asks "can I run this command?" and you just want it to stop asking.
You have two options, and they work very differently.
The two permission systems
# Option 1: Skip all permission checks (session-level)
claude --dangerously-skip-permissions
# Option 2: Pre-approve specific tools (config-level)
# In settings.json:
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git *)",
"Write(src/**)"
]
}
}
What --dangerously-skip-permissions actually does
This flag disables the entire human-in-the-loop confirmation system for that session.
Claude can:
- Run any bash command without asking
- Write to any file without asking
- Execute any tool without asking
The name is honest. It is dangerous. But it's also how you run Claude Code in headless/automated mode.
When it's appropriate:
- CI pipelines where there's no human to confirm
- Sandboxed Docker containers with no production access
- Local dev environments where you trust the codebase
When it's not appropriate:
- Any environment with production credentials
- Projects with sensitive files
- When you haven't audited the CLAUDE.md instructions
What allowedTools does instead
allowedTools in settings.json lets you pre-approve specific tool patterns. Claude asks about everything else.
{
"permissions": {
"allow": [
"Bash(npm test)",
"Bash(npm run lint)",
"Bash(git status)",
"Bash(git diff)",
"Read(**)",
"Write(src/**)",
"Write(tests/**)"
],
"deny": [
"Bash(rm *)",
"Bash(curl *)",
"Bash(wget *)"
]
}
}
This is the surgical approach. Claude runs pre-approved commands freely, asks about anything unexpected, and is blocked from destructive commands.
The glob patterns for allowedTools
The pattern syntax matters:
Bash(npm *) → any npm command
Bash(git *) → any git command
Write(src/**) → any file under src/
Write(*.md) → any markdown file in current dir
Read(**) → read any file
Practical config for a Node.js project:
{
"permissions": {
"allow": [
"Bash(npm *)",
"Bash(node *)",
"Bash(git status)",
"Bash(git diff)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(cat *)",
"Bash(ls *)",
"Read(**)",
"Write(src/**)",
"Write(tests/**)",
"Write(*.md)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(pip *)"
]
}
}
Project-level vs user-level permissions
You can set permissions at two levels:
User-level (~/.claude/settings.json):
- Applies to all projects
- Good for always-safe commands like
git status,ls,cat
Project-level (.claude/settings.json in your repo):
- Applies only to this project
- Good for project-specific build commands
- Committed to git so the team shares the same config
# Check what's currently allowed
cat ~/.claude/settings.json
cat .claude/settings.json # project-level
The hook alternative
For more complex permission logic, hooks give you programmatic control:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 /home/user/check-command.py"
}
]
}
]
}
}
Your check-command.py receives the tool input and can approve, block, or modify the command before it runs. This is how you build custom security policies.
Rate limits and permission fatigue
One underrated cost of the confirmation system: when Claude is frequently pausing to ask permission, it breaks the session flow. Each pause interrupts the reasoning chain.
For local development, a pre-approved allowedTools config eliminates 90% of interruptions while keeping dangerous operations gated.
If you're hitting rate limits on top of permission fatigue — Claude Code pausing both to ask permission AND hitting Anthropic's API limits — the underlying infrastructure is working against you.
Setting ANTHROPIC_BASE_URL to a rate-limit-free proxy (simplylouie.com runs one at $2/month) removes the API limit side of the equation, so the only pauses left are the ones you explicitly kept in your permissions config.
Quick reference
| Approach | Scope | Risk | Use case |
|---|---|---|---|
--dangerously-skip-permissions |
Session | High | CI, sandboxed containers |
allowedTools in settings.json |
Permanent | Low | Daily dev workflow |
| Hooks with custom script | Per-tool | Configurable | Complex security policies |
| No config (default) | All tools ask | None | Sensitive/unfamiliar codebases |
The default (ask for everything) is correct when you're in an unfamiliar codebase. allowedTools is correct for your regular projects. --dangerously-skip-permissions is only correct when you've explicitly sandboxed the environment.
Using Claude Code with rate limits? simplylouie.com runs a Claude proxy at $2/month — set ANTHROPIC_BASE_URL and the overloaded errors stop.
Top comments (0)