'Yes to all' is a permission policy: writing Claude Code allow, ask and deny rules that hold up against compound commands and wildcards
Every 'Yes, and don't ask again' writes a rule. Turn that habit into a reviewed .claude/settings.json: deny beats ask beats allow, why Bash(git * main) also allows git push, how compound commands are split, and when dontAsk or the sandbox beats bypassPermissions.
if.if.codesOct 18, 2026 · 7 min read#claude-code#permissions#security#developer-toolsAI-assisted
There is a post doing the rounds on X about clicking "Yes" to everything Claude Code asks. It's funny because it's how a lot of us work. Every "Yes, and don't ask again" writes a rule to disk, though. So you already have a permission policy, just not one you chose. This guide turns that habit into a .claude/settings.json you can read, review and commit. Every rule shape and behaviour below comes from the Claude Code permissions docs, quoted where precision matters.
What "Yes to all" actually saves
When you pick "Yes, and don't ask again" for a Bash command or a WebFetch domain, the docs say Claude Code "saves the rule to .claude/settings.local.json at the root of the git repository". File edits are different: that approval "lasts until the session ends".
Compound commands are split before saving: "approving git status && npm test saves a rule for npm test". The docs add that "Up to 5 rules may be saved for a single compound command." Open your settings.local.json now. The rules in there are your current policy.
The one rule to remember: deny, then ask, then allow
The docs:
The hierarchy of permissions: deny, then ask, then allow.
Rules are evaluated in order: deny, then ask, then allow. The first match in that order determines the outcome, and rule specificity doesn't change the order.
Two consequences matter. First, "An allow rule can't carve an exception out of a deny rule": a deny of Bash(aws *) blocks aws s3 ls even if you allowed exactly that. Second, the same holds between ask and allow: "a matching ask rule prompts even when a more specific allow rule also matches the same call." So write deny and ask rules for what must never slip through, and keep allow rules narrow.
Why Bash(git * main) also allows git push
A * in a Bash rule "matches any text, including spaces". What limits a rule is the literal text before the first *: "Claude Code matches everything before the first * as written, so those words are what limit the rule." The docs' matching table shows what goes wrong with a wildcard in the middle:
A misplaced wildcard can grant more access than intended.
Bash(git * main) matches git merge main, git push origin main and git -c core.fsmonitor=<script> diff main.
Bash(git log * main) matches git log --oneline main but not git push origin main.
The -c case is the nasty one. The docs point out it "makes git run a program you name". The advice is direct: "Put the * after the subcommand." Claude Code also "warns at startup about an allow rule with a * before the subcommand, such as Bash(git * main)."
Two smaller details from the same table:
The space matters.Bash(ls *) matches ls -la and ls, but not lsof. Bash(ls*) matches lsof too.
No * means exact.Bash(npm run build) does not match npm run build --watch.
How compound commands are split
From the docs: "The recognized command separators are &&, ||, ;, |, |&, &, and newlines. A rule must match each subcommand independently." So Bash(npm run test *) does not approve this:
Compound commands are broken down into individual rules.
npm run test && git push origin main
The first half matches the allow rule. The second half has to clear the rules on its own. With the config below it hits an ask rule, so you get a prompt.
Deny and ask rules work the other way: they "apply when any subcommand matches them, including a command nested inside a subshell, a command substitution, or a control-flow body such as a for loop." The docs' example: an ask rule Bash(git clean *) "still prompts you for cd /tmp && git clean -f or echo "$(git clean -f)"". One edge case: a dangling operator such as npm test && is treated as unparseable, so Bash(npm *) won't approve it.
A few wrappers are stripped before matching (timeout, time, nice, nohup, stdbuf, command, builtin, zsh's noglob, and bare xargs), so Bash(npm test *) matches timeout 30 npm test. Runners like npx, docker exec and devbox run are not stripped. The docs warn that Bash(devbox run *) matches "whatever comes after run, including devbox run rm -rf .".
Need the backend underneath done properly?I build and harden Go and Python backends. The estimate is free.
What a deny rule is not
The docs are blunt here. A Bash deny or ask rule "covers the invocation Claude usually produces and isn't a security boundary around the program." Bash(git push *) stops git push origin main but not git -C . push origin main or git 'push' origin main. Bash(rm *) doesn't stop /bin/rm -rf build/ or bash -c 'rm -rf build/'. Your other rules and the permission mode decide those cases. For enforcement that doesn't depend on command text, the docs point to the sandbox, covered below.
The same goes for Read deny rules. They cover Claude's file tools and commands Claude Code recognises, like cat, but not "a Python or Node script that opens files itself".
The settings.json
Put this in .claude/settings.json and commit it. Settings files "are strict JSON: a // comment or a trailing comma is a syntax error". List keys such as permissions.allow merge across files, so your settings.local.json rules still apply alongside these.
{"$schema":"https://json.schemastore.org/claude-code-settings.json","permissions":{"allow":["Bash(npm run lint)","Bash(npm run test *)","Bash(npm run build)","Bash(git diff *)","Bash(git add *)","Bash(git commit *)","Edit(/src/**)","Edit(/tests/**)","WebFetch(domain:code.claude.com)"],"ask":["Bash(git push *)","Bash(git clean *)","Bash(git reset *)","Bash(npm install *)"],"deny":["Bash(git push --force *)","Bash(git push * --force)","Bash(rm -rf *)","Bash(curl *)","Bash(wget *)","Read(./.env)","Read(./.env.*)","Read(./secrets/**)"]}}
Why each block looks the way it does:
allow: every Bash rule puts its * after the subcommand. Commands like ls, cat, grep and read-only git are missing on purpose. Claude Code already runs its built-in read-only set "without a permission prompt in every mode".
Edit paths: in project settings, a leading / anchors at the primary working directory, so Edit(/src/**) means your project's src/, not the filesystem root. For absolute paths the docs use //.
ask: git push * prompts even inside compound commands, because ask rules match any subcommand.
deny: blocking curl and wget while allowing WebFetch(domain:...) follows the docs' own advice for URL filtering. Force-push is denied in both flag positions, and the deny wins over the git push * ask rule because deny is checked first. As the section above says, none of this stops git -C . push --force.
Check what loaded with /permissions. It "lists all permission rules and the settings.json file each rule comes from".
dontAsk, sandbox or bypassPermissions?
If clicking "Yes" was really about not being interrupted, look at a mode instead of a broader wildcard.
dontAsk "Auto-denies every call that would otherwise prompt", while still running reads, read-only commands and your allow rules. The modes page lists it as best for "Locked-down CI and scripts", with this example:
claude -p "run the test suite" --permission-mode dontAsk --allowedTools "Bash(npm test)""Read"
The sandbox is an OS-enforced boundary around shell commands. You turn it on with /sandbox or "sandbox": {"enabled": true}. In its auto-allow mode, Claude Code "approves a command automatically, with no prompt, when the command runs inside the sandbox." This answers "stop asking me about every shell command" better than a wildcard does, because the limit no longer depends on how the command is spelled. Two caveats: "The sandbox covers shell commands only," so file tools still follow your permission rules. And to stop Claude retrying a failed command outside the sandbox, set "allowUnsandboxedCommands": false.
bypassPermissions "Skips permission prompts, except for the actions no mode auto-approves", including writes to protected paths like .git and .claude. The docs' instruction is: "Only use this mode in isolated environments like containers or VMs where Claude Code can't cause damage." It also can't be switched on from a repo: setting it as defaultMode in .claude/settings.json or .claude/settings.local.json "doesn't take effect", and the session starts in Manual mode.
Ten-minute checklist
Open .claude/settings.local.json and read every rule your past "Yes" clicks saved.
Rewrite any rule with a * before the subcommand, and heed the startup warning if you see it.
Add deny rules for things that must never run, and ask rules for things you want to see first.
Commit the shared rules in .claude/settings.json. Check them in /permissions.
If you're still clicking "Yes" all day, turn on the sandbox rather than widening the wildcards.