Claude Code

    How to Stop Claude Code Asking Permission for Everything

    A copyable settings.json allowlist for Claude Code, plus the five permission rules that look like they work and silently do not, without skipping permissions.

    13 min read
    How to Stop Claude Code Asking Permission for Everything

    Nothing kills an agentic workflow faster than a permission prompt every eleven seconds.

    It is worse the further you are from the keyboard. Supervising a Claude Code session from your phone is pleasant right up until the twentieth Allow tap, at which point you go back to your desk, which was the thing you were trying to avoid.

    The usual fix people land on is --dangerously-skip-permissions. The flag is named honestly, and on a machine that holds your credentials it is the wrong answer.

    Here is the middle: a real allowlist you can paste, and the rules that look like they work and quietly do not.

    First, decide which lever you are pulling

    There are two independent controls and conflating them is why people end up with a config that does nothing.

    Permission modes set the session baseline: what runs without asking at all.

    ModeWhat runs without asking
    default (shown as Manual)Reads only
    acceptEditsReads, file edits, and common filesystem commands (mkdir, touch, mv, cp)
    planReads, plus classifier-approved commands where auto mode is available
    autoEverything, with background safety checks by a second model
    dontAskOnly pre-approved tools; everything else is denied, never prompted
    bypassPermissionsEverything

    Permission rules layer on top, naming specific tools and commands to pre-approve or block.

    The interaction is the part worth memorising: deny rules block in every mode, including bypassPermissions. Allow rules have no effect in bypassPermissions. So a session running with the dangerous flag ignores everything you carefully allowed, and still honours everything you denied.

    On Pro, Max, and Team plans, auto is the built-in starting mode. If prompts are still constant, you are likely in Manual mode because a settings file put you there.

    A starting allowlist you can paste

    Put this in your project's .claude/settings.json. It is deliberately conservative: the commands are ones whose blast radius is a rerun, and the deny list covers the things you do not want happening while you are on a train.

    {
      "permissions": {
        "allow": [
          "Bash(npm run *)",
          "Bash(npm test *)",
          "Bash(npm ci)",
          "Bash(git status *)",
          "Bash(git diff *)",
          "Bash(git log *)",
          "Bash(git add *)",
          "Bash(git commit *)",
          "Bash(git checkout -b *)",
          "Edit(src/**)",
          "Edit(tests/**)",
          "Read(**)"
        ],
        "deny": [
          "Bash(git push *)",
          "Bash(npm publish *)",
          "Bash(curl *)",
          "Bash(wget *)",
          "Read(./.env)",
          "Read(./.env.*)",
          "Read(./secrets/**)"
        ],
        "ask": ["Bash(git rebase *)", "Bash(git reset --hard *)"]
      }
    }
    

    What each block is doing:

    • allow removes the prompts you would have approved anyway. Bash(npm run *) covers every script in your package.json without enumerating them.
    • deny is the real safety work. Note curl and wget: without them, Bash cannot reach arbitrary URLs, which is the gap that makes a WebFetch domain allowlist meaningful. Anthropic's docs are explicit that "using WebFetch alone doesn't prevent network access. If Bash is allowed, Claude can still use curl, wget, or other tools to reach any URL."
    • ask forces a prompt even where an allow rule would otherwise match. Use it for the operations you want to see but not forbid.

    You do not need Bash(ls *), Bash(cat *), or Bash(grep *). Claude Code recognises a built-in read-only set that runs without prompting in every mode: ls, cat, echo, pwd, head, tail, grep, find, wc, which, diff, stat, du, cd, and read-only forms of git. Adding rules for those is noise.

    Run /permissions to see every active rule and which settings file each one came from. You can open it while Claude is working, and a change takes effect from the next tool call in the same turn.

    The five rules that look like they work and do not

    This is the part the reference documentation contains but nobody assembles, and it is where an afternoon goes.

    1. Bash(git * main) is not a git rule, it is almost a shell

    The wildcard stands in for whatever text is in its place. In Bash(git * main) that is the subcommand, so it matches git merge main and git push origin main.

    It also matches git -c core.fsmonitor=<script> diff main, and -c core.fsmonitor makes git run a program you named. A rule that reads like "git operations on main" is closer to "run an arbitrary program."

    The rule: put the * after the subcommand. Bash(git log *) allows only git log. Claude Code warns at startup about an allow rule with a * before the subcommand.

    The same shape bites elsewhere. Bash(* --version) puts the wildcard where the program goes, so bash -c 'echo hi' --version matches.

    2. Write(...) and Glob(...) path rules are silently never consulted

    Claude Code checks file permissions against Edit(path) and Read(path) rules only.

    Write a path rule for Write, NotebookEdit, Glob, or the legacy MultiEdit and Claude Code accepts it, warns at startup, and then never consults it. The rule sits in your settings file looking like protection.

    The rule: use Edit(docs/**) in place of Write(docs/**), and Read(docs/**) in place of Glob(docs/**).

    3. Path rules anchor somewhere you did not expect

    Read and Edit rules use gitignore pattern syntax, with four distinct forms:

    PatternResolves to
    //pathAbsolute path from the filesystem root
    ~/pathPath from your home directory
    /pathPath relative to the settings source
    path or ./pathPath relative to the current directory

    The third is the trap. A deny rule Read(/secrets/**) written in your user settings (~/.claude/settings.json) anchors at that settings source, so it blocks ~/.claude/secrets/** and not the secrets directory in your project.

    The rule: for a user-level rule that should apply inside every project, use a // absolute path or a ~/ home-relative path. /path is for project settings.

    4. Argument-constraining Bash rules are fragile by design

    Anthropic warns about this directly. A rule like Bash(curl http://github.com/ *) intends to restrict curl to GitHub, and misses:

    • Options before the URL: curl -X GET http://github.com/...
    • A different protocol: curl https://github.com/...
    • A redirect: curl -L http://short.example.com/xyz landing on GitHub
    • A variable: URL=http://github.com && curl $URL
    • An extra space: curl http://github.com

    The rule: do not try to constrain arguments with a Bash pattern. Deny the network tools outright and allow specific domains through WebFetch(domain:github.com), or enforce it in a PreToolUse hook.

    5. Environment runners hand the wildcard straight back

    Before matching, Claude Code strips a fixed set of wrappers, so Bash(npm test *) also matches timeout 30 npm test. The stripped list is timeout, time, nice, nohup, stdbuf, the shell builtins command and builtin, zsh's noglob, and bare xargs.

    Development environment runners are not on that list, and they execute their arguments. A rule Bash(devbox run *) matches devbox run rm -rf .. The same applies to npx, mise exec, direnv exec, and docker exec.

    The rule: write one rule per inner command you actually want, such as Bash(devbox run npm test). Exec wrappers like watch, setsid, ionice, and flock cannot be prefix-approved at all and always prompt in Manual mode, as do find -exec and find -delete.

    Two more things that will make you think your config is broken

    defaultMode: "auto" in project settings does nothing

    You can set permissions.defaultMode in a settings file to choose the starting mode. But if you set "auto" in .claude/settings.json or .claude/settings.local.json, the value does not take effect, and Claude Code then uses the built-in default rather than a defaultMode from ~/.claude/settings.json. Set "bypassPermissions" in those two files and the session starts in Manual mode instead.

    This is deliberate: a checked-in repository file must not be able to widen permissions for everyone who clones it. The other values apply from any settings file.

    Project allow rules wait for workspace trust

    permissions.allow rules and permissions.additionalDirectories in a project's .claude/settings.json grant capability, so Claude Code applies them only after you accept the workspace trust dialog for that folder. The dialog lists what the folder would grant, so you can read it first. deny and ask rules are unaffected, since they only restrict.

    If your carefully written project allowlist appears inert on a fresh clone, this is why.

    Precedence, in one paragraph

    Permission rules follow normal settings precedence, with managed settings highest: nothing, including command-line arguments, overrides a managed rule.

    Beyond that, one asymmetry decides most conflicts. If a tool is denied at any level, no other level can allow it. A user-level deny blocks a project-level allow and vice versa, because deny rules from every scope are evaluated before allow rules. The same holds between ask and allow: a matching ask rule prompts even when a more specific allow rule also matches.

    A practical consequence: a broad deny like Bash(aws *) cannot carry allowlist exceptions. Bash(aws s3 ls) in allow will not rescue it.

    One more distinction worth knowing. A deny rule that is a bare tool name, like Bash, removes the tool from Claude's context entirely so it never sees it. A scoped rule like Bash(rm *) leaves the tool available and blocks matching calls when Claude tries them.

    Choosing your approach

    Allowlists are not the only way to cut prompts, and they are not always the best one.

    SituationReach forWhy
    A repo you work in dailypermissions.allow + deny in .claude/settings.jsonExplicit, reviewable, shareable with the team
    Iterating on code you are watchingacceptEdits modeEdits stop prompting; shell commands still do
    Long unattended task on a trusted machineauto modeA classifier reviews actions instead of you
    Fewer prompts, no classifierManual mode + the Bash sandbox in auto-allowThe sandbox boundary substitutes for the whole-tool prompt
    CI with an exact command list--permission-mode dontAsk --allowedTools "Bash(npm test)" "Read"Anything unlisted is denied, never prompted
    A disposable container--dangerously-skip-permissionsOnly where Claude Code cannot cause damage

    The sandbox row deserves a note, because it is the least-known option and often the right one. Turn on the Bash sandbox (/sandbox, or sandbox.enabled in settings) and leave autoAllowBashIfSandboxed at its default of true, and sandboxed Bash commands run without prompting even against a bare Bash ask rule: the isolation boundary substitutes for the prompt. Deny rules still apply, and content-scoped ask rules like Bash(git push *) still prompt. It is available on macOS, Linux, and WSL2.

    Where the circuit breakers stay wired

    Even with everything above turned up, a short list is never auto-approved in any mode, bypassPermissions included:

    • Tools matched by an explicit ask rule
    • Tools that require user interaction, including AskUserQuestion
    • rm and rmdir targeting a critical path, which no allow rule and no PreToolUse hook returning "allow" can approve
    • Writes to protected paths such as .git and .claude outside bypassPermissions

    That last one has a wrinkle worth knowing: permissions.allow rules do not pre-approve protected-path writes at all. The safety check runs before allow rules are evaluated, so an entry like Edit(.claude/**) changes nothing. In modes that prompt, the .claude/ prompt offers Yes, and allow Claude to edit its own settings for this session, which is the intended path.

    Redirects are checked separately

    A subtle one that produces confusing prompts. When a command redirects output, Claude Code checks the target against your file rules as if Claude had written that file directly.

    So Bash(git commit *) allows the command and not the target: git commit -m x > /tmp/out.txt gets an additional check on /tmp/out.txt against your Edit rules, protected paths, and working directories. A target starting with ~ or containing a glob always needs approval.

    Targets with no file behind them are not checked: /dev/null, file-descriptor forms like 2>&1, and here-docs.

    If you keep getting a prompt for a command you allowed, look at what it is writing to.

    Putting it together

    Start with three moves, in this order.

    1. Run /permissions and read what is already active and where it came from. Half the time the surprising rule is one you approved months ago with "Yes, and don't ask again."
    2. Paste the allowlist above into .claude/settings.json, then trim it to your project. Every rule you cannot justify out loud comes out.
    3. Add the deny list before you need it. git push, npm publish, curl, wget, and your secrets paths. This is the block that lets you say yes to everything else.

    Then, if you are supervising sessions away from your desk, the prompts that remain should be ones you genuinely want to see on a phone screen. That is the point.

    Working out how much autonomy to hand an agent in your stack? Book a free consultation with Evalics and we will look at where the boundaries belong.

    Official Sources

    By Kevin Michael Schindler, AI Automation Expert at Evalics

    Ready to automate your business?

    Book a free consultation and discover how AI automation can save you hours every week.

    Frequently Asked Questions