Why does Claude Code keep asking for permission?
Answer
If you have not configured anything, this is by design: Claude Code starts in Manual mode, which asks before edits and before commands outside a small read-only set. If you did pick a mode and it still asks, six things prompt regardless — explicit ask rules, protected paths such as .git and .claude, and anything outside the mode's scope among them.
What it does
If you have not configured anything, this is probably Manual mode working as designed: it asks before every file edit, every shell command outside a small read-only set, and every network call. The prompt is the review step, not a misconfiguration.
Whether you start there depends on your plan. Pro, Max and Team sessions start in auto mode; everything else starts in Manual. The status bar tells you which — a grey ⏸ manual mode on against ⏵⏵ auto mode on.
That read-only set is worth knowing, because it explains why some commands never ask: ls, cat, echo, pwd, head, tail, grep, find, wc, which, diff, stat, du, cd, and read-only forms of git. You cannot add to the set, but an ask or deny rule for one of these commands still takes effect — rules are evaluated before the set is consulted.
The rest of this page is about the other case — you picked a mode that should have removed the prompt, and it appeared anyway. Six documented things prompt regardless of the mode.
Explicit ask rules. A rule you wrote with type ask forces a prompt even in auto and bypassPermissions. That is its job. The one exception is dontAsk, where there is nobody to prompt, so the same rule becomes a refusal.
Connector tools your organisation set to ask. A connector is an MCP server your organisation manages centrally rather than one you added yourself; an admin can mark its tools as always-ask. These prompt even when one of your allow rules matches.
MCP tools that require interaction. A server can mark a tool with _meta["anthropic/requiresUserInteraction"], and it prompts in every mode. Requires Claude Code v2.1.199 or later.
Writes to protected paths. .git, .claude, .vscode, .idea and others — the directories holding repository state and Claude's own configuration — are never auto-approved except in bypassPermissions, which also lifts the block during planning in sessions where it is available.
Removals targeting / or your home directory. In modes other than auto, these prompt as a circuit breaker against model error. In auto mode the classifier — the second model that reviews actions in your place — decides them.
Anything outside the mode's scope. acceptEdits covers edits and seven filesystem commands inside your working directory. A path outside that scope, or any other shell command, is not something the mode ever claimed to approve.
How to check
Read the prompt itself first. It names the tool and the target, which is usually enough to tell a protected path from an out-of-scope path from an ask rule.
Two of the six — a protected path, or something outside the mode's scope — are settled before your rules are consulted at all, so no allow entry will silence them.
For the rest, your rules are evaluated in a fixed order:
deny → ask → allow
The first match wins, and how specific a rule is does not change that. A broad deny beats a narrow allow; a matching ask prompts even when an allow also matches. Only when nothing matches does the mode decide — and in auto mode, the classifier decides in its place.
That order is the whole explanation for the most confusing case: a rule you wrote having no visible effect. Either something earlier in the chain matched first, or the call never reached the chain.
How to fix it
Match the fix to the cause, and resist escalating the mode to silence one command.
- One command asks repeatedly — add a
permissions.allowrule for that
command. It fixes the specific prompt without removing every other one.
- The prompt is a protected path — allow rules cannot help here. The prompt
itself offers a per-session approval; take that instead.
- You want fewer prompts on edits — that is
acceptEdits, not a rule.
- You want no prompts at all in a throwaway environment — that is
bypassPermissions, with the caveats on that page.
Example
Rules live under permissions in a settings file — .claude/settings.json for the project, ~/.claude/settings.json for you everywhere. All three types share one shape:
{
"permissions": {
"allow": ["Bash(npm test:*)"],
"ask": ["Bash(git push:*)"],
"deny": ["Read(./.env)"]
}
}
Each entry is Tool or Tool(specifier). The tool is the one Claude would call — Bash, Read, Edit, WebFetch — and the specifier narrows it. What goes in the parentheses depends on the tool: a command for Bash, a path for the file tools, domain:example.com for WebFetch.
Without a wildcard the match is exact: Bash(npm run build) covers that command and nothing else. A trailing :* is shorthand for a trailing *, so Bash(npm test:*) matches npm test with any arguments. It only works at the end — in Bash(git:* push) the colon is just a character. A bare Bash with no parentheses matches every shell command.
allow runs without asking, ask forces a prompt even in modes that would not have asked, deny refuses outright.
Now the case that catches people. A rule like this looks like it should stop the prompts for Claude's own config:
{
"permissions": {
"allow": ["Edit(.claude/**)"]
}
}
It does not. The protected-path check runs before Claude Code evaluates allow rules from settings, so the per-mode outcome is unchanged: default and acceptEdits still prompt, dontAsk still denies.
What does work is the prompt itself. For a .claude/ write it offers:
Yes, and allow Claude to edit its own settings for this session
That approves later .claude/ writes for the rest of the session.
Common mistakes
Reading a prompt in auto mode as a bug. Explicit ask rules are designed to survive it.
Blaming the mode for out-of-scope paths. acceptEdits is bounded by your working directory and additionalDirectories. A prompt for a path outside them is the mode working as documented — --add-dir is the fix.