Permission Configuration: You Decide How Loose or Tight
📚 Series Navigation: The previous article 19 Context Management taught you how to manage Claude's "workbench" and prevent it from overloading its memory. This article changes the dimension—it doesn't manage "how much it remembers," but rather "how much it dares to touch": from asking you for every single line of command to going fully automatic, you decide how tight to hold the permission reins.
"How dare you enable --dangerously-skip-permissions? It literally has 'dangerous' in its name."
"I'm in a sandbox, what's there to be afraid of? Even if it does rm -rf on the whole directory, it just deletes a disposable container, and recreating one brings it right back."
I've stood on both sides of this conversation: when running batch refactoring in an isolated container in the cloud, I kept the loosest dangerous mode on the whole time, and had zero psychological burden deleting and restarting; but once I returned to my local machine directory containing the team's real code, I had to weigh even acceptEdits for a couple of seconds. Put simply, there is no "right or wrong" when it comes to permissions, only "where you use them". The same loosest dangerous mode is a "speed booster" in an isolated container, but a "time bomb" on the machine containing your company's production code.
Earlier in chapter 07 we had a brief taste—Claude will stop and ask you before modifying files. That was just the tip of the iceberg. Today we'll uncover the entire iceberg: what permission modes Claude Code has, how to switch with one click, and how to use configuration files to precisely specify "this is allowed, that is absolutely forbidden".
After reading this article, you will get:
- A clear table explaining what the six permission modes are and what scenarios they should be used in respectively
- The muscle memory of
Shift+Tabto switch between modes, and how to specify the mode directly upon startup - The syntax for writing
allow/ask/denyrules insettings.jsonto precisely control permissions by tool and by command - Two configuration templates for "relaxed toy projects, tightened production projects" that you can copy and use immediately
- When exactly you should dare to enable
--dangerously-skip-permissions, and where the red line is
01 Understand First: What the Permission System is Managing
Let's give the conclusion first: Claude Code is by default an "ask first, act later" intern, and the permission configuration is the "code of conduct" you set for this intern.
It categorizes all operations into three types, with completely different default treatments. This classification table in the official documentation is the foundation for understanding everything that follows:
| Tool Type | Example | Default Requires Approval |
|---|---|---|
| Read-only | Read files, Grep search | No, pass directly |
| Bash Command | Execute shell commands | Yes |
| File Modification | Edit / Write to modify files | Yes |
Analogy: Does the intern ask you before taking action. A reliable intern will just casually take a look when you ask him to "look at this piece of code" (read-only, zero risk); but if he is going to "modify production configurations" or "run a deletion command", he would normally look up and ask you, "Boss, can I touch this?". Claude Code defaults to this kind of person—read freely, but report before acting.
There is a crucial realization here, and I myself have stumbled over it: Permissions are hard-enforced by the Claude Code program, not by relying on the "model being conscious".
I once specifically wrote the sentence "do not execute git push" in CLAUDE.md, thinking that would lock it down, but it turned out it still pushed swiftly when it needed to—because CLAUDE.md is only a soft prompt that "affects what it wants to do", the real hard constraints must be written in the permission rules. Later, when I moved this rule into deny, it became well-behaved. The official statement is very straightforward:
Permission rules are enforced by Claude Code, not by the model. Your prompts or instructions in
CLAUDE.mdaffect what Claude attempts to do, but they do not change what Claude Code is allowed to do.
Remember this rule, and you will know where to build the "defense line".
💡 One-sentence summary: Read-only is freely allowed, and taking action requires approval is the default code of conduct; to truly block a certain operation, you must write permission rules, just giving instructions in
CLAUDE.mdis useless.
02 Six Permission Modes: From "Ask Every Step" to "Fully Open"
The permission mode controls one thing: the "frequency" at which Claude pauses to ask you before acting. From "stopping at every step to wait for your nod" to "doing it directly without asking anything", it is a continuous spectrum.
Analogy: Still that intern, the mode is the "level of autonomy" you give him. On the first day he arrives, everything must be asked for instructions (default); after getting familiar, he doesn't need to ask to modify code, but he still needs to call you before dropping a database (acceptEdits); you are on a business trip and trust him completely, letting him figure things out himself (bypassPermissions).
The official provides a total of six modes. Organizing their "will it ask you first" and "applicable scenarios" into a table—this is the most important one to remember in this article:
| Mode | What can be done without asking | Will it ask you first | Best Scenario |
|---|---|---|---|
default | Only read-only | Modifying files, running commands both will ask | Getting started, sensitive work |
acceptEdits | Read-only + file editing + common file system commands (mkdir, mv, cp, etc.) | File editing and the above file system commands won't ask, other Bash commands will still ask | Iterating code you are reviewing |
plan | Only read-only (only researches, only makes plans, won't touch your source code) | Same prompting rules as default | Explore and make a plan before making changes |
auto | All operations, with background classifier safety checks | Basically won't ask, out-of-bounds ones are blocked by the classifier | Long tasks, reducing interruptions (research preview) |
dontAsk | Only pre-approved tools | Doesn't ask and doesn't stop, unapproved ones are directly rejected | Locked down CI, scripts |
bypassPermissions | All operations, skips all checks | Completely won't ask | Only isolated containers / VMs |
A few points that beginners are most likely to confuse, picked out and explained clearly:
plan (Plan Mode) is not "relaxing", it is exactly the most restrained. It allows Claude to only read files and only run read-only commands to figure out the situation, and then write you a plan of "how I intend to change it", but it won't touch a single word of your source code. When taking over any unfamiliar project, the first step is recommended to switch to plan and let it read through first, which is much more stable than letting it make blind changes right from the start.
acceptEdits is the sweet spot for daily development. It automatically approves file edits and several common file system commands (mkdir, touch, rm, rmdir, mv, cp, sed) in your working directory, but other shell commands, and writes outside the working directory, will still stop and ask you. It is equivalent to "modifying code without clicking agree every time, but keeping the brakes for dangerous actions".
Both auto and bypassPermissions look like they "don't ask", but the security is worlds apart. auto is a research preview version, with an independent classifier model behind it reviewing every operation before it happens, and out-of-bounds ones (such as curl | bash, pushing to main, deleting cloud storage) will be blocked; bypassPermissions is truly running naked, it doesn't even prevent prompt injections. The official states clearly:
bypassPermissionsoffers no protection against prompt injections or unintended actions. For background safety checks without prompts, use auto mode instead.
So if you want to "save worry but have a bottom line", prioritize auto, don't just jump to bypassPermissions right away.
💡 One-sentence summary:
defaultasks every step,acceptEditsdoesn't ask for modifying code,planonly looks and doesn't move,autohas a classifier as a safety net,bypassPermissionsis completely running naked—remember this spectrum from strict to loose, and just take your seat accordingly.

This image arranges the six modes into a spectrum "from strictest to loosest": the green area on the left is plan / default (read-only, asking every step, the safest), moving to the right through acceptEdits (modifying code without asking), auto (classifier safety net), all the way to the red area bypassPermissions on the right (fully running naked)—the further right, the redder the color, reminding you "the looser it is, the more clearly you need to see where you are using it".
03 Switching Modes: Shift+Tab One-Click Loop
Now that you know the modes, how do you switch? The most commonly used is just one shortcut: Shift+Tab.
Casually pressing Shift+Tab in a session will loop through three modes:
default → acceptEdits → plan → (press again to return to default)To know which mode you are currently in, just look at the status bar. For example, when switching to acceptEdits, the status bar will display ⏵⏵ accept edits on.
Pay attention to an official detail, so you don't press it for half a day and can't find a certain mode: The default loop only has default / acceptEdits / plan, these three. The other three are entered in different ways: auto will automatically appear in the loop after the account meets the conditions; bypassPermissions must be started with a flag like --permission-mode bypassPermissions to enter the loop; dontAsk never appears in the loop, and can only be set with the --permission-mode dontAsk startup parameter (explained below).
Analogy: The "ring / vibrate / mute" three-stage toggle on a phone. You toggle that switch next to the volume buttons, and it cycles through these three gears. Shift+Tab is the toggle switch for Claude Code, cycling through "ask every step / modify code without asking / only look, don't move".
If you don't want to switch manually every time you enter, there are two ways to "nail down" the mode:
Method 1: Specify with a parameter at startup (only applies to this session):
claude --permission-mode planYou can change plan to any mode name like acceptEdits, dontAsk, etc. bypassPermissions is a bit special—--permission-mode bypassPermissions and --dangerously-skip-permissions are equivalent ways to write it, but the latter name "with a built-in warning" is generally used daily, which will be detailed in section 05.
Method 2: Write it into settings.json as the default (takes effect on every startup). In your project's .claude/settings.json:
{
"permissions": {
"defaultMode": "acceptEdits"
}
}⚠️ An explicitly stated official restriction: When
defaultModeis set to"auto", the ones in the project and local settings will be ignored (to prevent a certain repository from secretly enabling automatic mode for itself), you must write it in the user-level~/.claude/settings.jsonto default toauto.
A recommended habit: Don't nail down the mode in the project, rely entirely on Shift+Tab to switch manually. Because for the same project, sometimes you want to let it work freely (switch to acceptEdits), sometimes you only want it to come up with a plan (switch to plan), and hardcoding it is actually awkward. defaultMode is generally only set to default as a safety net when "this project just needs to be strict".
💡 One-sentence summary: In a session,
Shift+Tabloops through the three gears of "ask every step / modify code without asking / only look, don't move", look at the status bar for the current gear; if you want to nail it down, use the--permission-modestartup parameter ordefaultModeinsettings.json.
04 Fine-Grained Permission Control: allow / ask / deny Three Rules
The mode is a "coarse adjustment", setting a broad tone. The "fine adjustment" that truly pinpoints exactly to "this command can be run, that is absolutely forbidden" relies on the three rules in settings.json.
Every rule ultimately falls into one of three actions:
| Action | Effect | Typical Use |
|---|---|---|
allow | No approval needed, automatically pass | Low-risk, high-frequency operations, such as git status, npm run build |
ask | Pop up a prompt, let you make the call | A bit risky, want to keep a confirmation, such as git push |
deny | Directly block, no execution and no prompt | Explicitly forbidden dangerous operations, such as rm -rf, reading .env |
Priority is an iron law: deny → ask → allow, the first matching rule wins. So deny always overrules the other two—if you write both allow and deny, deny has the final say. This design is very reasonable: "Forbidden" should carry more weight than "allowed".
The rule syntax is ToolName or ToolName(specifier). Looking at a few examples will make it clear:
| Rule | What it matches |
|---|---|
Bash | All Bash commands |
Bash(npm run build) | Only matches exactly this command npm run build |
Bash(npm run *) | Matches commands starting with npm run (build, test...) |
Read(./.env) | Reads the .env file in the current directory |
WebFetch(domain:github.com) | Fetches network requests for github.com |
The wildcard * has a space pitfall that beginners always step into, and the official has specifically emphasized it:
Bash(ls *)matchesls -labut notlsof, whereasBash(ls*)matches both.
With just one space difference, the meaning changes. ls * (with space) requires a space after ls, so lsof slips through the net; ls* (without space) matches lsof as well. If you want to be precise, include the space.
A complete piece of configuration looks like this—allowing npm and git commit, but blocking git push:
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git commit *)"
],
"deny": [
"Bash(git push *)"
]
}
}The last security key point must be highlighted: The deny rule for Read / Edit cannot block "detour reading and writing" in Bash child processes.
What does this mean? You write deny: Read(./.env) to block Claude from directly reading .env, but if it runs a piece of Python script open('.env').read(), this deny can't control it—because that is a child process reading it, and doesn't go through Claude's built-in file tools. The official reminder:
They do not apply to arbitrary child processes that indirectly read or write files, like Python or Node scripts that open files themselves. For OS-level enforcement that prevents all processes from accessing a path, enable the sandbox.
This point is easy to surprise people—thinking that deny .env is absolutely safe. So if you really want to lock down sensitive files, permission rules + Sandbox must be used together to be called defense in depth (the sandbox is OS-level isolation, and the next article "Security" will expand on it).
💡 One-sentence summary: Match according to this priority
deny → ask → allow,denyis always the biggest; whether the rule has a space means different things; butdenycannot stop scripts from taking a detour to read and write, sensitive files must be configured with a sandbox.
05 Relax for Toys, Tighten for Production: Two Templates + "Dangerous Mode" Red Line
After talking about a bunch of mechanisms, the practical implementation boils down to one sentence: The more of a "toy" the project is, the more you can relax; the more "production" it is, the more you must tighten.
Here are two sets of standing configurations, choose one based on the nature of the project.
Template 1: Toy / Personal small project (relax to speed up). If you mess up, just rebuild it, there is no need to stop at every step. Set to acceptEdits to let it modify code without asking, and only catch a few truly dangerous ones:
{
"permissions": {
"defaultMode": "acceptEdits",
"deny": [
"Bash(rm -rf *)",
"Bash(git push *)"
]
}
}Template 2: Production / Company project (tighten for gatekeeping). By default ask at every step, read operations are opened up to make it convenient to check code, writing files and dangerous commands must pass through your hands, and sensitive files are directly blocked:
{
"permissions": {
"defaultMode": "default",
"allow": [
"Bash(git status *)",
"Bash(git diff *)",
"Bash(npm run *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(git push *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}When writing these two denys, remember: as mentioned in the previous section, the deny blocking .env cannot prevent scripts from taking a detour. If the production environment really needs to be stable, you have to stack a sandbox outside this layer. Don't treat a single layer of deny as an iron wall.
Finally, there is that red line, which is also the protagonist of the conversation at the beginning—--dangerously-skip-permissions (equivalent to bypassPermissions mode).
This thing skips all permission checks and safety checks, and tool calls execute immediately. The dangerously in its name is not a bluff. The official has drawn boundaries for its use:
Only use this mode in isolated environments (like containers, VMs, or dev containers without internet access) where Claude Code cannot cause harm to your host system.
The following set of iron rules can be copied directly:
| Scenario | Do you dare to open --dangerously-skip-permissions |
|---|---|
| Isolated container / VM / dev container | ✅ Dare, deleting it just deletes a disposable environment |
| Running one-off tasks in CI | ✅ Dare, but pair with deny to catch another layer |
| Your own local machine for daily development | ❌ Don't open, rather be slow than leave no brakes |
| Machine containing company production code | ❌ Absolutely don't open, this is a bomb |
Two "safeties" designed by the official make you slightly relieved: First, even in this mode, operations that delete root/home directories like rm -rf / and rm -rf ~ will still prompt, acting as a circuit breaker for slipping hands; Second, on Linux / macOS, starting this mode as root or sudo is directly rejected. But don't rely on the safeties—the core is "only open in an isolated environment that you won't feel bad about deleting".
💡 One-sentence summary:
acceptEditsspeeds up toy projects,defaultgatekeeps production projects, two templates to copy and use immediately;--dangerously-skip-permissionsis only opened in isolated containers, and is absolutely out of the question for local and production machines.
06 Hands-on: Configure Your First Set of Permission Rules in 5 Minutes
Just looking without practicing is a fake trick. Below I'll take you to configure a set of permission rules for a toy project, to see with your own eyes how allow and deny take effect. The whole process does not rely on any complex environment.
Step 1: Create a toy project and configuration file (Mac / Linux)
mkdir perm-demo
cd perm-demo
mkdir .claudeExpected: There is an empty .claude directory inside the perm-demo folder. Typing ls -a will show that .claude is there.
Step 2: Write a settings.json
Use your handy editor, and paste this into perm-demo/.claude/settings.json:
{
"permissions": {
"defaultMode": "default",
"allow": [
"Bash(git status *)"
],
"deny": [
"Bash(git push *)"
]
}
}The meaning of this set of rules: By default ask every step, but pass git status without asking, and block git push directly.
Step 3: Start Claude and check the rules
claudeOnce inside, type:
/permissionsExpected: A permission management interface pops up, where you can see the rules you just wrote—git status * is in the Allow list, git push * is in the Deny list, and they are marked with which settings.json file they came from. Seeing these two = the configuration has been correctly loaded.
Step 4: Verify that deny really blocks
Tell it to do a forbidden thing in the input box:
Help me execute git push origin mainExpected: Claude will not execute it, nor will it pop up a prompt asking "whether to approve"—it will directly tell you that this operation was rejected by the permission rules. This is the power of deny: no execution, no prompt, blocked all the way.
Step 5: Contrast the smoothness of allow
Then let it do a passed thing:
Help me take a look at git statusExpected: Because it hit allow: Bash(git status *), it ran it directly without popping up an approval prompt (this directory hasn't had git init yet, so the command itself will report "not a git repository", but that's git's business—the key is it didn't stop to ask you, which means allow took effect).
Running through these five steps, you have personally verified the complete link of "writing rules → loading → deny blocking it dead → allow passing it through". From now on, any permission configuration is essentially adding rules on top of this mechanism.
💡 One-sentence summary: Create
.claude/settings.jsonto write rules,/permissionsto see if it loads, and then let Claude run one forbidden and one allowed command respectively—personally running through this link is more effective than memorizing ten syntaxes.
07 Summary
In this article, we smoothed out the "permission reins" of Claude Code from beginning to end—how loose or how tight it can be, is entirely within your single thought and a few lines of configuration.
Stringing together the core points to review:
| What you want to do | Use what | Key Point |
|---|---|---|
| Adjust the "frequency of asking you" | Six permission modes | A spectrum from default (ask every step) to bypassPermissions (running naked) |
| Quickly switch modes in a session | Shift+Tab | Loops in the three gears of default / acceptEdits / plan |
| Nail down the default mode | defaultMode | Write into settings.json; auto must be placed at the user level |
| Precise control of a single operation | allow / ask / deny | Priority deny → ask → allow, deny is the biggest |
| Lock down sensitive files | deny + sandbox | Just deny cannot prevent scripts taking detours |
You should now be able to: Understand what scenarios the six permission modes are respectively used in, use Shift+Tab to switch casually, write allow / deny rules by tool and by command in settings.json, configure appropriately loose or tight permissions for toy projects and production projects respectively, and know clearly that the red line of --dangerously-skip-permissions can only be touched in isolated environments. This "freely loose and tight" ability to control permissions is your confidence to dare to let Claude work without overturning the car.
Next article 21 "Security and Risk Boundaries"—this article taught you "how to configure permissions", but configuration is just a tool. The higher-level question is: Should you really trust AI to touch your code and systems? Which scenarios are truly high-risk zones? What do pitfalls like prompt injection and sensitive data leakage look like? Now that you have the reins in hand, the next article discusses the judgment of "when to rein in and when to let go".