BubbleCodeFounding access

Claude Code permission modes, explained

Updated July 27, 2026 · 7 min read

Claude Code's permission mode decides which actions run silently and which stop to ask you. Picking one is a trade between how far an agent gets unattended and how much you're willing to have happen without a human looking — and no mode removes the need for someone to answer when it does ask.

Verified against Claude Code 2.1.220

Every flag and mode name below was checked against claude --version 2.1.220 on macOS. Permission modes have changed between releases — run claude --permission-mode with a junk value to have your own build print the list it accepts.

Permission mode is the single setting that decides how autonomous a Claude Code session is. It controls one thing: which tool calls run immediately, and which ones stop and wait for a human to say yes. Everything else about unattended agent work — parallel sessions, hooks, notifications — is downstream of this choice.

The modes the CLI accepts

You can see the authoritative list for your own build by passing an invalid mode. The CLI's argument parser prints the allowed set:

bash
$ claude --permission-mode nonsense -p "hi"
error: option '--permission-mode <mode>' argument 'nonsense' is invalid.
Allowed choices are acceptEdits, auto, bypassPermissions, manual, dontAsk, plan.
ModeWhat it stops asking aboutReasonable use
manualNothing — every tool call that needs permission asks.Unfamiliar repos, anything touching production, first run in a new project.
acceptEditsFile edits within the working directory. Commands still ask.The common working default. Claude rewrites files freely; Bash still stops at you.
planNothing — but it also can't act. Claude researches and proposes, then waits for approval to execute.Scoping a change before you let it touch anything.
autoWhatever a built-in classifier judges low-risk. Riskier calls still ask.Long refactors where you trust the blast radius but want a backstop.
dontAskPrompts are suppressed; denied actions are skipped rather than escalated to you.Batch/CI runs where a stall is worse than a skipped step.
bypassPermissionsEverything. No permission check runs at all.Disposable sandboxes with no credentials and no network. See the risk model below.

`default` still works, but isn't listed

On 2.1.220 the error message above omits default, yet claude --permission-mode default -p "say ok" runs fine — it's an accepted alias for the implicit starting mode. Don't build tooling on undocumented aliases; prefer manual or acceptEdits explicitly.

Setting a mode instead of passing a flag

A flag applies to one session. To make a mode stick for a project, set permissions.defaultMode in settings. Claude Code merges settings from user, project, and local scopes — see the annotated settings.json reference for how the layers resolve.

json
{
  "permissions": {
    "defaultMode": "acceptEdits",
    "allow": [
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(npm run test *)"
    ],
    "ask": [
      "Edit(.github/**)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(git push *)",
      "Read(./.env)"
    ]
  }
}

The allow / deny / ask rules are the part most people underuse. They're more precise than the mode itself: a mode is a blunt global posture, whereas a rule targets one tool and one argument pattern. In practice the durable setup is a conservative mode plus a growing allow list of the specific commands you've decided are boring.

Rule syntax, briefly

Rules are strings of the form Tool(specifier), and the CLI's own validation message spells out the contract: *Permission rules must be in an array. Format: ["Tool(specifier)"]. Use * for wildcards.*

  • Exact matchBash(npm run test) matches only that command.
  • Wildcard matchBash(git *) matches git, git status, git commit, and so on. This is the current form.
  • Prefix match (legacy)Bash(npm run:*) still works and the CLI labels it *legacy*. Prefer the space-wildcard form in new config.
  • Path globsEdit(docs/**) or Read(~/.zshrc) scope a tool to files rather than the whole repo.
  • Bare tool namesRead on its own allows the tool everywhere. Useful in allow, dangerous by habit.
  • deny takes precedence over allow. Write the deny rules first and treat them as a floor you don't lower.

There's a third array alongside them: ask, which forces a prompt even in a mode that would otherwise auto-approve. It's the right tool for the handful of paths you always want to see — CI config, migrations, anything with a deploy attached.

Which mode for unattended work

This is the question that actually matters if you're running several agents while doing something else. The honest answer is that there isn't a mode that makes an agent both safe and unattended — that's the trade the setting exists to express.

  • bypassPermissions gets you full autonomy by removing the safety property entirely. It's defensible in a container with no credentials; it's not defensible on your laptop, where the agent inherits your SSH keys, your npm token, and your shell history.
  • dontAsk avoids the stall without going fully open — but it silently skips work, so you come back to a task that's 'done' with pieces quietly missing.
  • acceptEdits or auto keeps the safety property and accepts that the session will eventually stop and wait. Which means the real constraint is not the mode. It's how long the agent sits blocked before a human notices.

That last point is the one worth internalising. With a conservative mode, agent throughput is bounded by your notification latency, not by the model. An agent that blocks for forty minutes because the prompt was behind a fullscreen window costs you forty minutes of wall-clock progress, and no permission setting fixes that. See why Claude Code stalls waiting for permission for the measurable version of this argument.

Locking the escape hatch

If you're setting this up for a team, or just for a future version of yourself at 2am, two settings are worth knowing:

json
{
  "permissions": {
    "defaultMode": "manual",
    "disableBypassPermissionsMode": "disable"
  }
}

disableBypassPermissionsMode removes the ability to opt into the no-checks mode from that settings scope. There's also a separate CLI flag, --allow-dangerously-skip-permissions, which *enables the option to bypass* without turning it on — the CLI's own help recommends it only for sandboxes with no internet access. Both exist because the failure mode they guard against is real and unrecoverable.

A setup that holds up

  1. 01Start every new project in manual. You'll learn what the agent actually wants to run before you decide what to hand over.
  2. 02Move to acceptEdits once you trust the repo boundary. Edits are cheap to review in git diff; commands are not.
  3. 03Add deny rules for the irreversible things — git push, rm -rf, reads of .env — before you add a single allow rule.
  4. 04Grow the allow list from the prompts you find yourself approving on autopilot. If you've said yes to npm run test:* thirty times, that's data.
  5. 05Fix your notification path. A conservative mode is only viable if being asked is cheap.

FAQ

What is the default permission mode in Claude Code?

A fresh session starts in a conservative mode where file edits and commands both require approval. On 2.1.220 you can name it explicitly as `default`, though the CLI's own error message lists only acceptEdits, auto, bypassPermissions, manual, dontAsk, and plan.

What is the difference between acceptEdits and bypassPermissions?

acceptEdits auto-approves file edits but still asks before running commands. bypassPermissions disables permission checking entirely — no tool call asks for anything, including shell commands. They are not adjacent settings; acceptEdits keeps a human in the loop for the dangerous category, bypassPermissions does not.

How do I set a permission mode permanently?

Set `permissions.defaultMode` in a settings.json file rather than passing --permission-mode per session. Project-level settings apply to everyone working in that repo; user-level settings in ~/.claude/settings.json apply everywhere.

Can I allow specific commands without switching modes?

Yes, and it's usually the better move. permissions.allow takes rules like Bash(npm run test *) or Edit(docs/**) that auto-approve a narrow pattern while everything else keeps asking. deny rules take precedence over allow.

Keep reading