
TL;DR
Stop the approval-fatigue prompts without going full YOLO mode. A hands-on guide to Claude Code's permission system - settings.json scopes, allow/deny/ask rules, tool specifiers, and the headless flags that actually matter.
| Resource | Link |
|---|---|
| Claude Code settings reference | code.claude.com/docs/en/settings |
| Permissions and rule syntax | code.claude.com/docs/en/permissions |
| Permission modes | code.claude.com/docs/en/permission-modes |
| CLI reference | code.claude.com/docs/en/cli-reference |
| Hooks reference | code.claude.com/docs/en/hooks |
If you use Claude Code for more than a few minutes a day, you have felt the friction: the agent stops to ask before every npm test, every file edit, every git status. The instinct is to reach for --dangerously-skip-permissions and never look back. That is a mistake. The permission system is the one layer standing between an autonomous agent and your shell, your secrets, and your production credentials. The right move is not to disable it - it is to configure it so the safe stuff runs silently and the dangerous stuff still stops.
This is a practical guide to that configuration. We will cover where settings live and how the scopes combine, the exact syntax for allow / deny / ask rules, how tool specifiers work for Bash, file edits, and web access, and the headless flags you need for CI. Every claim here is checked against the current Claude Code docs - links are in the table above and in the Sources section.
Last updated: June 17, 2026
Claude Code reads settings from several settings.json files. Crucially, permission rules merge across scopes rather than fully overriding one another - so a deny rule defined anywhere stays in force, and a more restrictive scope cannot be loosened by a less authoritative one. The scopes, from highest authority to lowest:
/Library/Application Support/ClaudeCode/; on Linux/WSL, /etc/claude-code/). Developers cannot override it. This is how a security team enforces a baseline.--allowedTools that apply to a single session..claude/settings.local.json. Personal, not checked into git (Claude Code adds it to .gitignore automatically). Machine-specific tweaks go here..claude/settings.json. Checked into the repo and shared with your team. This is the file that should encode "everyone on this project can run the test suite without asking."~/.claude/settings.json. Your global defaults across every project.The key mental shift: this is not a simple "higher file wins" override. The allow, deny, and ask lists from every applicable scope are combined, and within that combined set a deny rule can never be cancelled by an allow rule (more on that next). So put team-wide policy in the project's .claude/settings.json so it travels with the repo, keep personal preferences in ~/.claude/settings.json, and use .claude/settings.local.json for one-off machine-specific rules you do not want to commit.
A minimal project settings.json:
{
"permissions": {
"allow": [
"Bash(npm run test *)",
"Bash(npm run lint)",
"Read(~/.config/**)"
],
"ask": [
"Bash(git push *)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Bash(curl *)"
]
}
}
The permissions object holds three arrays that decide what happens when the agent wants to use a tool:
allow - the tool call runs without prompting you.ask - the tool call always prompts for confirmation, even if a broader allow rule would have matched.deny - the tool call is blocked outright and never runs.The precedence is the part people get wrong: rules are evaluated deny then ask then allow, first match wins, and a deny rule cannot have allowlist exceptions. So a deny for Read(./.env) blocks the file even if you also have a broad allow like Read(./**) - and even if that allow lives in a more authoritative scope. This is exactly the behavior you want: your allowlist can be generous because your denylist is the real safety net.
defaultMode)Tool calls that match none of the three lists fall back to the session's permission mode. Set the default under permissions with defaultMode; the documented values are:
default - prompt on first use of each tool (standard interactive behavior).acceptEdits - auto-approve file edits and common filesystem commands in the working directory, but still gate other tools.plan - Claude reads and explores but does not edit source files.dontAsk - auto-deny tools unless explicitly pre-approved via allow rules.bypassPermissions - skip all permission prompts (isolated environments only).auto - auto-approve with background safety checks (research preview; gated by account tier).{
"permissions": {
"defaultMode": "acceptEdits",
"deny": ["Read(./.env)", "Read(./.env.*)"]
}
}
One gotcha worth knowing: in recent versions, defaultMode: "auto" set inside a project's .claude/settings.json or .claude/settings.local.json is silently ignored, so a checked-out repo cannot grant itself auto mode. If you want auto mode to persist, put it in your user ~/.claude/settings.json.
Get the weekly deep dive
Tutorials on Claude Code, AI agents, and dev tools - delivered free every week.
From the archive
Jun 17, 2026 • 11 min read
Jun 17, 2026 • 7 min read
Jun 17, 2026 • 8 min read
Jun 17, 2026 • 9 min read
Each rule is a tool name, optionally followed by a specifier in parentheses that narrows which calls it matches. A bare tool name ("WebSearch") matches every use of that tool. The specifier syntax differs by tool, and the differences matter.
Bash rules match against the command string. The space before * is significant:
"Bash(npm run build)" // exact command match
"Bash(npm run test *)" // prefix match: "npm run test" plus any arguments
"Bash(npm *)" // any npm command
"Bash(ls *)" // matches "ls -la" but NOT "lsof"
"Bash(ls*)" // matches both "ls -la" and "lsof"
"Bash(*)" // every command (same as bare "Bash")
Compound commands are understood: Claude Code splits on &&, ||, ;, |, and newlines, and each subcommand must match independently, so an allowlisted command chained with a disallowed one will still stop. Common process wrappers (timeout, time, nice, nohup) are stripped before matching.
The important caveat: Bash matching is still best-effort, not a hardened shell sandbox. Do not rely on a Bash allow rule as a security boundary against a hostile prompt; rely on deny plus a restricted environment for that.
File-tool rules take a path argument that follows gitignore-style glob patterns. A leading // means an absolute path, ~/ is your home directory, a leading / is project-relative, and a bare filename matches at any depth:
"Edit(/src/**/*.ts)" // project-relative; ** crosses directories
"Read(~/.zshrc)" // a specific home-directory file
"Read(//tmp/scratch)" // absolute path
"Edit(.env)" // bare filename matches at any depth
This is the mechanism for the single most valuable permission rule you can write: deny reads of your secrets.
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(.env)",
"Read(./secrets/**)",
"Read(/**/*.pem)"
]
A deny on Read does more than stop the file from being opened - it keeps credentials out of the model's context entirely, which is the actual goal. The denylist above is a reasonable baseline for any repo.
Web tools support a domain: specifier so you can scope network access to trusted hosts. Matching is case-insensitive:
"allow": [
"WebFetch(domain:docs.anthropic.com)",
"WebFetch(domain:*.github.com)"
],
"ask": [
"WebSearch"
]
Note that WebFetch(domain:*.github.com) matches subdomains but not bare github.com, and WebFetch(domain:example.*) matches TLD variations like example.org.
Tools provided by MCP servers are addressed as mcp__<server>__<tool>. You can allow an entire server or a single tool:
"allow": [
"mcp__github", // every tool from the github MCP server
"mcp__sentry__list_issues" // just one tool from the sentry server
]
Spawning subagents is gated by the Agent tool, which takes the agent name as a specifier - and, for deny/ask rules, can match on parameter values:
"allow": ["Agent(Explore)"],
"ask": ["Agent(isolation:worktree)"],
"deny": ["Agent(model:opus)"]
You do not have to hand-edit files. Inside a session, run /permissions (alias /allowed-tools) to open an interactive view of every active allow/deny/ask rule, add or remove rules, manage working directories, and review recent denials. When Claude Code prompts you to approve a tool call, choosing the "always allow" option writes the corresponding rule into your settings for you - usually .claude/settings.local.json. That is the fastest way to build an allowlist: work normally for an afternoon, approve the repetitive-but-safe calls with "always," and let the file accumulate. (There is no claude config CLI command; /config opens the settings UI interactively, and --settings overrides for one session.)
The additionalDirectories setting (under permissions) grants the agent access to directories outside the current working directory without re-approving each one - handy for monorepos or sibling asset folders:
{
"permissions": {
"additionalDirectories": ["../shared-types", "../design-tokens"]
}
}
When you run Claude Code non-interactively - in CI, a cron job, or a script - there is no human to answer prompts, so permission handling has to be settled up front. The relevant CLI flags:
--allowedTools - a list of tool rules to allow for this run, same syntax as the allow array.--disallowedTools - the inverse, for this run.--permission-mode - sets the mode for the session (default, acceptEdits, plan, dontAsk, bypassPermissions, or auto).--settings - inject a settings object or file for this run.--dangerously-skip-permissions - bypass all permission checks. This is the YOLO flag (equivalent to --permission-mode bypassPermissions).A headless run that is allowed to edit and test but nothing else:
claude -p "fix the failing unit tests in src/parser" \
--allowedTools "Edit(/src/**)" "Bash(npm run test *)" "Read(/src/**)" \
--permission-mode acceptEdits
About --dangerously-skip-permissions: it has its place - a fully sandboxed throwaway container with no network and no real credentials, where the blast radius is genuinely zero. It does not belong on your laptop or any machine with SSH keys, cloud credentials, or access to production. The name is not a joke. If you reach for it to escape prompt fatigue, that is a signal to write a good allow list instead, which gets you the same quiet experience without handing over the keys. For enforcing policy too dynamic for static rules, see our guide on Claude Code hooks.
permissions is one object in a larger settings file. A few neighbors that interact with it:
hooks - shell commands that fire on lifecycle events (PreToolUse, PostToolUse, and others). A PreToolUse hook can block a tool call programmatically, which lets you enforce rules too dynamic for static glob matching (for example, "deny any Bash command that contains a production hostname"). Permissions and hooks are complementary, not redundant.env - environment variables injected into every session and tool call.enableAllProjectMcpServers - auto-approves the MCP servers defined in the project's .mcp.json instead of prompting for each.disableBypassPermissionsMode - set to "disable" to forbid bypass mode entirely, useful in a managed policy.A sane default to drop into a project's .claude/settings.json, then tighten from experience:
{
"permissions": {
"allow": [
"Bash(npm run test *)",
"Bash(npm run lint)",
"Bash(npm run build)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)",
"Read(/**)",
"Edit(/src/**)",
"Edit(/tests/**)"
],
"ask": [
"Bash(git push *)",
"Bash(git commit *)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(.env)",
"Read(./secrets/**)",
"Read(/**/*.pem)",
"Bash(curl *)",
"Bash(rm -rf *)"
]
}
}
This lets the agent test, lint, build, read the codebase, and edit source and tests without interruption; pauses it before it pushes or commits; and hard-blocks it from reading secrets or running a handful of dangerous commands. Commit it, and your whole team inherits the policy.
Three lists, one precedence rule (deny over ask over allow, with deny never overridable), a set of scopes whose rules merge rather than simply override, and a clear separation between static rules (permissions) and dynamic enforcement (hooks). Get those four things straight and you can run Claude Code with far less friction and far more safety than either extreme - clicking "approve" all day or skipping permissions entirely.
For the next layer up, see how to write a CLAUDE.md so the agent knows your conventions, and Claude Code tips and tricks for the broader workflow.
Rules are evaluated deny, then ask, then allow, and the first match wins. A deny rule blocks a tool call even if a broader allow rule would have matched it, and a deny cannot have allowlist exceptions - which is what makes a generous allowlist safe, because your denylist is the real boundary. Tool calls matching none of the lists fall back to the configured defaultMode.
In settings.json files at several scopes: managed policy (deployed by an organization, highest authority), command-line flags, project-local .claude/settings.local.json (gitignored, personal), project-shared .claude/settings.json (committed, team-wide), and user ~/.claude/settings.json (your global defaults). Rules from all applicable scopes merge, and a deny anywhere stays in force.
Add a prefix rule to the allow array, for example "Bash(npm run test *)", in your project's .claude/settings.json. The trailing * is a prefix match, so it covers npm run test, npm run test:unit, npm run test:e2e, and so on. The space before * matters. Or approve a test command once with the "always allow" option and Claude Code writes the rule for you.
Only in a fully sandboxed environment with no network access and no real credentials, where the agent cannot do lasting damage. On a machine with SSH keys, cloud credentials, or production access, it removes the one layer protecting those resources. For day-to-day prompt fatigue, write a good allow list instead - you get the same quiet experience without disabling the safeguards.
Add Read deny rules such as "Read(./.env)", "Read(./.env.*)", and a bare "Read(.env)" (which matches at any depth) in the deny array. Because deny wins over allow and cannot be overridden, these block the file even if you have a broad Read(/**) allow rule, and they keep secrets out of the model's context entirely rather than just declining to open the file.
Read next
CLAUDE.md is the highest-leverage file in any Claude Code project. Here's what goes in one, what doesn't, and the patterns that actually ship.
12 min readHooks give you deterministic control over Claude Code. Auto-format on save, block dangerous commands, run tests before commits, fire desktop notifications. Here's how to set them up.
12 min readThe definitive collection of Claude Code tips - sub-agents, hooks, worktrees, MCP, custom agents, keyboard shortcuts, and dozens of hidden features most developers never discover.
25 min readTechnical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.
Anthropic's agentic coding CLI. Runs in your terminal, edits files autonomously, spawns sub-agents, and maintains memory...
View ToolInteractive TUI dashboard that shows exactly where your Claude Code and Cursor tokens are going, in real time.
View ToolAnthropic's flagship reasoning model. Best-in-class for coding, long-context analysis, and agentic workflows. 1M token c...
View ToolMac app for running parallel Claude Code, Codex, and Cursor agents in isolated workspaces. Watch every agent work at onc...
View ToolPick the hooks you want, get a settings.json you can paste in.
View AppEvery coding agent in one window. Stop alt-tabbing between Claude, Codex, and Cursor.
View AppTurn a one-liner into a working Claude Code skill. From idea to installed in a minute.
View AppFires before any tool executes. Allow, deny, defer, or modify the call.
Claude CodeConfigure Claude Code for maximum productivity -- CLAUDE.md, sub-agents, MCP servers, and autonomous workflows.
AI AgentsA concrete step-by-step guide to moving your development workflow from Cursor to Claude Code - settings, rules, keybindings, and the habits that transfer.
Getting Started
Open Design: Open-Source n8n App That Turns Any Website into a Brand Kit, Design System, HTML + Images The video introduces Open Design, an MIT-licensed full-stack template that combines AI and n8n a...

Nimbalyst Demo: A Visual Workspace for Codex + Claude Code with Kanban, Plans, and AI Commits Try it: https://nimbalyst.com/ Star Repo Here: https://github.com/Nimbalyst/nimbalyst This video demos N...

Composio: Connect AI Agents to 1,000+ Apps via CLI (Gmail, Google Docs/Sheets, Hacker News Workflows) Check out Composio here: http://dashboard.composio.dev/?utm_source=Youtube&utm_channel=0426&utm_...

CLAUDE.md is the highest-leverage file in any Claude Code project. Here's what goes in one, what doesn't, and the patter...

Hooks give you deterministic control over Claude Code. Auto-format on save, block dangerous commands, run tests before c...

The definitive collection of Claude Code tips - sub-agents, hooks, worktrees, MCP, custom agents, keyboard shortcuts, an...

Databricks open-sourced Omnigent, a meta-harness that sits above individual agent CLIs so your sessions, policies, and s...

Claude Managed Agents is in public beta with solid sandboxing and session persistence - but the headline orchestration f...

Fable 5 drains the 5-hour rolling window dramatically faster than Opus or Sonnet. Here is what the plan multipliers actu...

New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.