Finding the right balance between safety and workflow speed
Claude Code asks for approval before every file edit and every Bash command, unless a developer deliberately configures different rules. This article explains the permission modes, uses settings.json and hook examples to show which actions can be safely automated, and where deliberate confirmation before destructive commands and remote pushes remains essential.
Table of Contents
- 1. Why the permission model sits at the core of Claude Code
- 2. Permission modes at a glance: default, auto-accept, plan, bypass
- 3. What can run without asking: reading, searching, analyzing
- 4. Approving file edits: Edit, Write, and MultiEdit
- 5. Controlling Bash commands: allow, ask, and deny rules
- 6. Git and remote operations: when a pause is mandatory
- 7. settings.json and hooks: enforcing rules project-wide
- 8. Team policies: shared configuration and onboarding
- 9. Limits of the model: false sense of security and residual risk
- 10. Summary
- 11. FAQ
1. Why the permission model sits at the core of Claude Code
Claude Code can read files, edit them, delete them, run Bash commands, and access the network, essentially anything the calling user is themselves authorized to do. That is exactly why the permission model is not a side feature but the central safety layer between a language-based assistant and a live development system. Without this layer, every suggestion would need to be trusted blindly before execution, a model that neither solo developers nor teams could sustainably work with.
The core idea is simple: low-risk, highly reversible actions, such as reading a file, should not slow down the workflow. High-risk or hard-to-reverse actions, such as deleting a directory or force-pushing, should force a deliberate human decision. This tradeoff between safety and speed runs through every configuration layer of Claude Code, from the command line to settings.json to project-wide hooks, all covered in detail in the sections below.
2. Permission modes at a glance: default, auto-accept, plan, bypass
Claude Code offers several permission modes that differ in their default stance toward asking for confirmation. The default mode asks before every file edit and every Bash command individually, with the option to approve a single action, all actions of that type, or all actions for the rest of the session. Auto-accept mode (acceptEdits) applies file edits automatically but still requires confirmation for Bash commands, useful for refactoring tasks that involve many small edits but no shell actions.
Plan mode executes no changes at all and only produces a plan for review, ideal for exploratory tasks or codebases that are still unfamiliar. Bypass mode (--dangerously-skip-permissions) disables all confirmation prompts entirely and should only be used in isolated environments such as containers or CI pipelines with no access to production systems. The name is deliberately chosen: enabling this mode in a normal development environment gives up the last layer of human control completely.
#!/usr/bin/env bash
# Claude Code CLI: permission mode per session
# Default mode: every file edit and every Bash command needs approval
claude
# Auto-accept file edits, but Bash commands still require approval
claude --permission-mode acceptEdits
# Planning only: no edits or commands are executed, only a plan is produced
claude --permission-mode plan
# Skip all permission prompts entirely (dangerous, use only in sandboxes/CI)
claude --dangerously-skip-permissions
# Headless mode with an explicit allow-list for a CI pipeline
claude -p "Update the changelog for this release" \
--allowedTools "Read" "Grep" "Edit(CHANGELOG.md)"
3. What can run without asking: reading, searching, analyzing
Read-only operations are the clearest case for automation without confirmation. Read, Grep, and Glob change no state, are repeatable at will, and only return information that Claude Code uses to plan further steps. A developer who had to manually confirm every read operation would effectively negate the tool's core advantage: fast exploration of large codebases.
Pure analysis commands like static code checks (phpstan, phpcs) or running an existing test suite in an isolated environment also fall into this low-risk category, as long as they touch no production data or external systems. The important distinction is between a command that returns information and a command that produces a side effect: git status and git diff are harmless, git commit already changes project state and belongs in a different category. That distinction is the foundation of any sensible allow-list.
4. Approving file edits: Edit, Write, and MultiEdit
File edits sit in a gray zone between low-risk and high-risk. Within a Git repository they are fundamentally reversible through version control, a faulty edit can be inspected with git diff and reverted with git checkout as long as it hasn't been committed yet. That is exactly why auto-accept mode for file edits is acceptable in many teams, while Bash commands continue to require individual confirmation.
The safety boundary shifts, however, once edits touch files outside the working directory, involve configuration files containing credentials, or sit right before an automatic commit. In settings.json, edit rules can be scoped to specific paths, for example Edit(src/app/code/**) as an allow rule combined with an explicit ask rule for .env or *.pem files. This granularity allows production code to be edited freely while sensitive configuration files still require deliberate confirmation.
5. Controlling Bash commands: allow, ask, and deny rules
Bash commands are the most powerful and simultaneously the riskiest tool category, because they give Claude Code access to the entire system, not just individual files. Claude Code therefore distinguishes three rule levels: allow for commands that run without confirmation, ask for commands that require explicit confirmation, and deny for commands that are blocked regardless of context. Deny rules always take precedence over allow rules, a command matching both lists gets blocked.
In practice, a combination works well: generous allow rules for recurring, harmless commands (npm test, git status, bin/magento cache:status) paired with explicit deny rules for known danger patterns. Wildcard patterns like Bash(git push --force*) also catch variants with extra flags. Important: an allow-list is not a substitute for a deny-list, since it only covers known-good cases, unknown commands land in ask mode either way and must be deliberately reviewed there.
{
"permissions": {
"allow": [
"Read",
"Grep",
"Glob",
"Bash(npm test)",
"Bash(php bin/phpunit *)",
"Bash(git status)",
"Bash(git diff*)",
"Bash(git log*)"
],
"ask": [
"Bash(npm install*)",
"Bash(git commit*)",
"Bash(git push*)",
"Edit",
"Write"
],
"deny": [
"Bash(rm -rf*)",
"Bash(git push --force*)",
"Bash(git reset --hard*)",
"Bash(DROP TABLE*)"
]
}
}
6. Git and remote operations: when a pause is mandatory
Git operations deserve separate treatment because they differ in whether their impact stays local or reaches the whole team. A local commit only affects the working directory and can be corrected with git reset or git commit --amend as long as it hasn't been pushed. A git push to a feature branch already has broader reach, but is usually still reversible. A git push --force on a shared branch or directly on main, however, overwrites history for the entire team and can irreversibly lose other people's commits.
The practical consequence: local commits can fall into the auto-accept bucket in trusted projects, while every push command should generally stay in the ask category, regardless of how routine a given workflow appears. Especially critical are commands that rewrite history (rebase -i on shared branches), delete tags, or remove remote branches. These operations are, for good reason, explicitly on the deny-list in many team configurations, even when an individual developer occasionally needs them deliberately, in which case running them manually outside Claude Code is the safer choice.
7. settings.json and hooks: enforcing rules project-wide
While permission modes control the behavior of a single session, the rules in .claude/settings.json apply project-wide and reload on every start. This file can be versioned and thus maintained alongside the code in the repository, changes to the security policy go through the same code review as any other change. For cases that go beyond static allow/deny patterns, Claude Code offers hooks: scripts that run automatically before (PreToolUse) or after (PostToolUse) a tool execution and can block the call based on arbitrary logic.
A PreToolUse hook receives the planned tool call as JSON on stdin and can prevent execution with a specific exit code. This allows checks that pure pattern matching cannot cover, for example forbidding write access outside a specific directory regardless of the exact command text. Hooks are therefore the more flexible, but also more maintenance-intensive, complement to the declarative rules in settings.json.
#!/usr/bin/env python3
"""PreToolUse hook: block destructive Bash commands before execution.
Reads the tool call as JSON from stdin, exits 2 to block with a message.
"""
import json
import re
import sys
DANGEROUS_PATTERNS = [
r"rm\s+-rf\s+/(?!home|tmp)",
r"git\s+push\s+.*--force",
r"git\s+reset\s+--hard",
r"DROP\s+TABLE",
r">\s*/dev/sd[a-z]",
]
payload = json.load(sys.stdin)
if payload.get("tool_name") != "Bash":
sys.exit(0)
command = payload.get("tool_input", {}).get("command", "")
for pattern in DANGEROUS_PATTERNS:
if re.search(pattern, command, re.IGNORECASE):
print(f"Blocked: command matches dangerous pattern '{pattern}'", file=sys.stderr)
sys.exit(2)
sys.exit(0)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/block-dangerous-commands.py"
}
]
}
]
}
}
8. Team policies: shared configuration and onboarding
Once several developers use Claude Code on the same project, an individual setting becomes a team question. A project-wide .claude/settings.json in the repository ensures everyone gets the same baseline rules without configuring them individually, new team members inherit the policy automatically when they clone the repository. Personal additions can be kept separately in a non-versioned settings.local.json, for example for individual credentials or project-unrelated helper tools.
For teams with varying experience levels, a conservative baseline configuration that is loosened gradually is preferable to the reverse. It's also critical to decide who can approve changes to the permission configuration itself: if settings.json can be changed through a normal pull request, that file should be subject to the same review requirements as deployment scripts or CI configuration, since it directly defines which automated actions may run on the team's behalf.
// ci-review.js: invoke Claude Code headlessly with a strict allow-list
const { spawnSync } = require('node:child_process');
const result = spawnSync('claude', [
'-p', 'Review the diff for obvious bugs and report findings only',
'--permission-mode', 'plan',
'--allowedTools', 'Read', 'Grep', 'Glob',
], { encoding: 'utf-8' });
if (result.status !== 0) {
console.error('Claude Code review failed:', result.stderr);
process.exit(1);
}
console.log(result.stdout);
9. Limits of the model: false sense of security and residual risk
The permission system reduces risk but does not eliminate it entirely. An allow rule like Bash(npm install*) looks harmless but can install a package with a malicious postinstall script without triggering any further confirmation. Prompt injection through content Claude Code reads while working, such as a manipulated README file or a compromised comment in a dependency, can also try to steer the model toward actions outside the actual task. The deny-list is therefore no substitute for basic caution with unfamiliar dependencies or repositories.
Realistically, the permission model is one defense layer among several, not the only one. Sandboxing at the operating system or container level, restricted database permissions for development environments, and regular audits of the settings.json rules complement the built-in safeguards. The overview below classifies typical actions by whether automatic approval is acceptable or deliberate confirmation remains mandatory.
| Action | Classification | Reasoning |
|---|---|---|
| Reading files (Read, Grep, Glob) | Safe to automate | Changes no state, repeatable at any time |
| Running local tests | Safe to automate | Runs isolated, no production data involved |
| File edits in the working directory | Automatable with review | Always inspectable via git diff before commit |
| rm -rf or git clean -fd | Confirmation required | Irreversibly deletes data outside version control |
| git push --force / push to main | Confirmation required | Overwrites remote history, affects the whole team |
| Production migrations and deploys | Confirmation required | Not reversible, affects live systems and real users |
Mironsoft
Magento and Hyvä development with AI-assisted workflows
Ready to integrate Claude Code safely into your workflow?
We set up permission rules, hooks, and team policies for Claude Code so automation speeds up delivery without giving up control over production Magento systems.
Permission audit
Review existing settings.json and hooks for gaps
Team configuration
Set up a shared policy for all developers and CI pipelines
CI integration
Integrate Claude Code safely into pipelines in plan mode
10. Summary
The Claude Code permission system addresses a fundamental problem of AI-assisted development: automation should speed up delivery without giving up control over production systems. Default mode asks before every file edit and every Bash command, auto-accept mode speeds up file edits, plan mode executes nothing at all, and bypass mode belongs exclusively in isolated environments. Read-only operations can be automated without hesitation, while destructive commands like rm -rf, git push --force, and production migrations generally deserve deliberate confirmation.
Project-wide rules in settings.json and flexible PreToolUse hooks make this policy reproducible and team-wide, rather than dependent on individual settings. It remains important to stay aware that the permission model reduces risk but does not eliminate it, prompt injection and manipulated dependencies require additional caution beyond pure allow and deny lists.
Claude Code Permissions - The Essentials at a Glance
Permission modes
Default asks about everything, acceptEdits speeds up edits, plan executes nothing, bypassPermissions only in sandboxes.
Safe to automate
Reading, searching, and local tests change no state and can run without confirmation.
Confirmation required
rm -rf, git push --force, and production migrations are not reversible and need deliberate confirmation.
Team policy
Versioned settings.json plus hooks make the rules reproducible and consistent across the team.