Configure Claude Code Approval Gates by Project Risk Level
You want Claude Code to keep moving on throwaway work and stay careful on production. The same global setting can't do both. Here's the three-tier config that matches gate strictness to actual project risk.
Claude Code gates certain operations — bash commands, file writes, network calls — behind approval prompts before executing them. These gates are appropriate for production work but create serious friction on throwaway experiments. The right configuration lives in settings.json, not CLAUDE.md, and differs by project risk level. This tutorial covers three profiles — permissive, balanced, and strict — with exact configuration patterns you can deploy before starting a session, so you're never trapped editing config files mid-run.
TL;DR
| Tier | Profile | Gate behavior | Use when |
|---|---|---|---|
| 1 | Permissive | Auto-approve reads, writes, all bash | Throwaway scripts, local experiments |
| 2 | Balanced | Auto-approve safe ops, gate destructive bash and network | Active feature development |
| 3 | Strict | Gate everything except reads and non-destructive git | Production configs, infra, migrations |
Set the right profile in settings.json before starting a session — you cannot reliably change it mid-run without restarting.
Why This Matters Now: Auto Mode Just Got Stricter
A recent Claude Code platform update tightened what Auto Mode silently approves. As developers quickly noticed in r/ClaudeCode, operations that previously ran without interruption now surface approval prompts — and the only fix is an explicit edit to settings.json.
The problem hit hardest for anyone managing sessions remotely. One developer put it plainly: "now needing explicit claude.md and settings.json edits... trying to get something done from my phone and not able to easily edit those files."
This compounded a frustration that predated the platform change. In r/ClaudeAI, developers have been asking for relief for months: "I get a lot of 'Do you want to proceed?' prompts... I want it to keep moving on a throwaway project without interrupting me every five minutes."
The answer isn't to nuke all gates globally — that's a documented security risk. Sonar's audit of pre-trust code execution in Claude Code showed exactly how dangerous it is to let agents run arbitrary operations before a trust decision is established. The answer is tiered configuration: match gate strictness to actual project risk before the session starts.
What Controls Approval Gates: settings.json vs CLAUDE.md
settings.json enforces gates at the system level. Tool call patterns in permissions.allow are auto-approved without prompting. Patterns in permissions.deny are blocked unconditionally. This is machine-enforced — the model has no say.
CLAUDE.md provides behavioral context, not enforcement. CLAUDE.md directives are visible to the model as project documentation and can nudge behavior. But they don't enforce anything at the permission layer. Write "always ask before running tests" in CLAUDE.md and the model may follow this — or stop following it after a dozen tool calls. The instruction compliance degradation past ~15 tool calls is a known problem in longer sessions. CLAUDE.md is useful for documenting intent; settings.json is what actually holds.
The settings.json hierarchy (each level overrides the one above it):
~/.claude/settings.json— global, applies to all projects on this machine.claude/settings.json— project-level, committed to the repo, shared with the team.claude/settings.local.json— project-local, not committed, personal overrides only
For a thorough audit of how these files interact with MCP configs and skills, see Managing Claude Code Config Sprawl. Tanium's analysis of Claude Code's source exposure surface is also worth reading if you're configuring gates in a team or enterprise environment.
Prerequisites
- Claude Code installed and authenticated
- A project directory with
.claude/initialized (runclaudeonce to create it) - Optional: Grass for mobile approval forwarding on Tier 2/3 sessions (recommended, not required — covered below)
Step 1: Choose Your Tier Per Project
Tier 1: Permissive — Throwaway and Experimental Projects
Use when you want zero interruptions and the project is isolated: throwaway scripts, local experiments, learning exercises, sandboxed repos with no credentials or external service connections.
Create .claude/settings.local.json in the project root (this file is not committed, so it stays local to your machine):
{
"permissions": {
"allow": [
"Bash(*)",
"Read(*)",
"Edit(*)",
"Write(*)",
"WebFetch(*)",
"WebSearch(*)"
],
"deny": []
}
}
Bash(*) matches any bash command — the agent runs anything without prompting. Do not use this tier on projects with environment variables, API keys, database connections, or any external service access.
Do not use --dangerously-skip-permissions as an equivalent shortcut. As TrueFoundry's breakdown explains, that flag bypasses the entire permission system including the deny list. With a settings.json Tier 1 profile, you still have a deny list you can add to if the agent heads somewhere unexpected. --dangerously-skip-permissions gives you no override path at all.
Tier 2: Balanced — Active Feature Development
Use for most day-to-day development: feature branches, non-production repos, shared team environments. Auto-approve safe, reversible operations. Gate destructive filesystem operations and external network calls.
Create .claude/settings.json in the project root (committed and shared with the team):
{
"permissions": {
"allow": [
"Read(*)",
"Edit(*)",
"Write(*)",
"Bash(git *)",
"Bash(npm *)",
"Bash(yarn *)",
"Bash(pnpm *)",
"Bash(node *)",
"Bash(python *)",
"Bash(python3 *)",
"Bash(pytest *)",
"Bash(jest *)",
"Bash(ls *)",
"Bash(cat *)",
"Bash(grep *)",
"Bash(find *)",
"Bash(echo *)",
"Bash(mkdir *)",
"Bash(cp *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(ssh *)",
"Bash(scp *)",
"Bash(docker rm *)",
"Bash(kubectl delete *)"
]
}
}
This auto-approves the bulk of what Claude Code does during normal development — reading files, editing code, running tests, git operations — while blocking destructive filesystem operations and external network calls. Prompts appear only for operations outside the allowlist, which is infrequent enough to maintain flow without sacrificing visibility.
deny patterns take precedence over allow. If a command matches both, it's blocked.
Tier 3: Strict — Production-Touching Work
Use for: production configuration changes, database migrations, infrastructure-as-code, anything that touches shared state or is hard to reverse.
Create .claude/settings.json for the project:
{
"permissions": {
"allow": [
"Read(*)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git show *)",
"Bash(ls *)",
"Bash(cat *)"
],
"deny": [
"Bash(rm *)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(ssh *)",
"Bash(scp *)",
"Bash(psql *)",
"Bash(mysql *)",
"Bash(kubectl *)",
"Bash(terraform *)",
"Bash(aws *)"
]
}
}
In Tier 3, only reads and non-destructive git inspection are auto-approved. Every write, edit, and non-trivial bash command surfaces a prompt. This slows the agent down — that friction is deliberate. For an even more conservative baseline, set both allow and deny to empty arrays to restore default behavior (prompt for every tool call).
Step 2: Document the Tier in CLAUDE.md
Even though CLAUDE.md doesn't enforce permissions, it's the right place to document which tier is active and why. Future team members (and Claude itself, during planning) benefit from seeing this context:
## Approval Gates
This project uses Tier 3 (strict) approval gate configuration in `.claude/settings.json`.
Only reads and non-destructive git commands are auto-approved. All writes, edits,
and bash commands outside git inspection require explicit approval.
Reason: this repo manages production Terraform and database migration scripts.
This creates a clean separation: CLAUDE.md explains intent; settings.json enforces it.
Step 3: Verify Your Gates Are Working
After deploying a profile, smoke-test before starting real work.
Check which settings file is active:
cat .claude/settings.json
cat .claude/settings.local.json
cat ~/.claude/settings.json
Tier 1 (permissive) smoke test:
Start a session and ask Claude to run ls -la. It should execute without a prompt. Then ask it to run curl https://example.com — this should also proceed, since Bash(*) matches everything.
Tier 2 (balanced) smoke test:
Ask Claude to run git status — should proceed without prompt. Ask it to run curl https://example.com — should prompt for approval. Ask it to run rm -rf /tmp/test-dir — should be blocked without prompting.
Tier 3 (strict) smoke test:
Ask Claude to run git diff HEAD — should proceed. Ask it to edit a file — should prompt. Ask it to run terraform plan — should be blocked.
If you're seeing unexpected behavior, the most common cause is pattern mismatch (see troubleshooting below).
Troubleshooting
"I updated settings.json but still get prompts for allowed tools."
The most common cause is pattern mismatch. "Bash(npm *)" matches npm install but not if your shell resolves the command differently. Compare the exact command string from Claude's tool call output against your allow patterns. Also verify which settings.json is actually being loaded — a global file may be overriding a project-level one, or the project file may not be in the correct .claude/ directory.
"Claude Code Auto Mode is still blocking operations that were previously auto-approved."
The recent Auto Mode tightening moved previously-implicit approvals into explicit gates. Add the specific tool call patterns that now prompt to your allow list. The platform change only affected implicit auto-approvals — deny list behavior is unchanged.
"My deny patterns aren't blocking certain commands."
Pattern matching works on the full command string from its first character — "Bash(npm *)" matches any command string starting with npm, not just the npm executable. "Bash(rm *)" blocks rm file.txt and rm -rf /tmp but not git rm file.txt, because git rm starts with git, not rm. Write deny patterns that match the exact command prefix — including the leading executable — for the operation you want to block.
"I need to change the tier mid-session from my phone."
Without a pre-configured allowlist, you're stuck — editing settings.json on a phone mid-session is exactly the problem the r/ClaudeCode thread on Auto Mode strictness surfaced. The solution is to pre-configure the right tier before starting. For approval prompts that still fire during Tier 2/3 sessions, the Grass section below addresses this directly.
"settings.json vs CLAUDE.md — which one wins for permission enforcement?"
settings.json always wins. CLAUDE.md influences model behavior through context, but if there's a conflict between what CLAUDE.md says and what settings.json allows or denies, settings.json is the binding rule. This is by design — the permission layer is separate from the model's instruction-following behavior.
How Grass Makes This Workflow Better
The three-tier config pattern works end-to-end without Grass. But there's a gap the config files alone can't close: approval prompts that fire when you're not at your desk, mid-session on a Tier 2 or Tier 3 project.
This is exactly the problem that surfaced in the Auto Mode strictness thread — after the platform change tightened gates, developers trying to manage sessions remotely found themselves stuck. The agent had hit a prompt, the session was blocked, and there was no way to resolve it from a phone without touching the config files to broaden the allowlist.
Pre-configuring the right tier before the session starts solves most of this. Grass solves the rest.
Permission forwarding to your phone. When Claude Code hits a Tier 2 or Tier 3 approval prompt — something not in your allow list — Grass surfaces that prompt on your phone as a native modal, formatted with the exact tool call and its input. Approve or deny with one tap. No terminal required, no config edits, no session restart. This is the full flow covered in How to Approve or Deny a Coding Agent Action from Your Phone.
Always-on sessions for Tier 3 work. A strict-mode session can sit blocked at a permission prompt for hours. On a laptop, that means the machine can't sleep without killing the session. Running the agent on a Grass cloud VM means the session stays alive, the approval modal waits on your phone, and your laptop is free. You handle the prompt when you're ready — not when your laptop's battery forces the issue.
Setup: install @grass-ai/ide globally (npm install -g @grass-ai/ide), run grass start in your project directory, scan the QR code with the Grass iOS app. The server runs on your local network — no cloud relay, no API keys sent to Grass, BYOK. The 5-minute setup guide covers the full flow.
For teams running Tier 3 sessions on shared infrastructure, the always-on cloud VM product at codeongrass.com adds persistent compute: agents run on a dedicated VM, accessible from any surface, with permissions forwarded to whoever needs to handle them.
FAQ
How do I stop Claude Code from asking for permission every five minutes on a throwaway project?
Create .claude/settings.local.json in your project root:
{
"permissions": {
"allow": ["Bash(*)", "Read(*)", "Edit(*)", "Write(*)"],
"deny": []
}
}
This auto-approves all tool calls. Use this only on sandboxed projects with no API keys, credentials, or external service connections.
What is the difference between settings.json and CLAUDE.md for controlling Claude Code's approval gates?
settings.json enforces gates at the system level — patterns in allow auto-approve tool calls, patterns in deny block them regardless of model behavior. CLAUDE.md provides context to the model and can influence behavior, but it is not a permission enforcement mechanism. For approval gate configuration, use settings.json. For documenting why a tier was chosen, use CLAUDE.md.
Why did Claude Code Auto Mode get stricter, and how do I fix it?
A recent platform update moved previously-implicit auto-approvals into explicit gates, requiring settings.json configuration. To restore prior behavior for specific tools, add their call patterns to permissions.allow in .claude/settings.json. The deny list behavior was not changed by the update.
What is the safest way to reduce approval prompts without disabling all gates?
Use the Tier 2 (balanced) profile: allowlist common safe operations (git, npm, reads, edits) and explicitly deny destructive operations (rm -rf, curl, ssh). This eliminates most flow interruptions while keeping hard blocks on high-blast-radius operations. The 3-checkpoint framework covers how to match gate placement to operation risk more precisely.
What does --dangerously-skip-permissions do and when should I use it?
It bypasses Claude Code's entire permission system — including deny lists — so no tool call will ever prompt for approval. It's appropriate only in fully sandboxed CI environments where the risk surface is explicitly accepted and understood. Never use it on a project with live credentials, external service connections, or shared infrastructure. TrueFoundry's breakdown covers the specific risks in detail.
How do I configure Claude Code approval gates without editing config files from my phone mid-session?
Pre-configure the right tier in settings.json before starting the session. For Tier 2 and Tier 3 projects where prompts are expected mid-run, use Grass — it forwards Claude Code's approval prompts to your phone as native modals you can resolve with a tap, without touching any config files.
Next steps:
- Copy the tier that matches your current project into
.claude/settings.local.json(Tier 1 or Tier 2 personal override) or.claude/settings.json(Tier 2 or Tier 3 for the whole team) - Run a quick smoke test before starting real work — verify that allowed operations proceed silently and that blocked operations actually block
- If you manage Tier 2 or Tier 3 sessions away from your desk, set up Grass so approval prompts reach your phone instead of blocking in a terminal