Paste a settings.json instead
An ask rule matches, so Claude Code always prompts, even if an allow rule also matches.
Why
npm testallow viaBash(npm test *)git pushask viaBash(git push *)
- Split into 2 subcommands. Deny and ask apply if any subcommand matches; allow needs every subcommand allowed.
Order: deny, then ask, then allow. Paths are resolved against a project at /project with home at ~/. This simulates the documented rules; built-in read-only command detection, redirects, sandboxing and hooks are not modelled.
Rule syntax cheat sheet
BashorBash(*): every command.Bash(npm run build): exactly that command.Bash(npm run *): that prefix.Bash(ls:*)is the same asBash(ls *).Read(.env): any.envunder the project.Read(./.env): the one at the root.Edit(/src/**): relative to the settings file's project.Read(~/.ssh/**): home.Read(//etc/**): absolute.WebFetch(domain:github.com): that host only.WebFetch(domain:*.github.com): subdomains.mcp__github: every tool from that server.mcp__github__get_*: matching tools.
Turn the rules you settle on into a file with the settings.json generator, or enforce something rules can't express with the hooks generator.
Frequently asked questions
How do Claude Code permission rules work?
Rules live in permissions.allow, permissions.ask and permissions.deny in settings.json and take the form Tool or Tool(specifier). Claude Code checks deny first, then ask, then allow. A deny rule anywhere wins, an ask rule beats an allow rule, and anything no rule covers falls back to the permission mode.
What does Bash(npm run *) match?
Any command that starts with "npm run ", plus bare "npm run" because a trailing " *" also matches the command without arguments. It does not match "npm install". Put the * after the subcommand: Bash(git *) would allow every git command, including push.
Does an allow rule cover chained commands?
Only if every part is allowed. Claude Code splits on &&, ||, ;, |, |&, & and newlines, so Bash(npm test *) does not approve "npm test && rm -rf dist". Deny and ask rules apply if any part matches.
How do I block Claude from reading secrets?
Add Read deny rules such as Read(./.env), Read(./.env.*) and Read(secrets/**). A bare filename like Read(.env) matches at any depth; a single-directory pattern like secrets/** in a deny rule also matches nested copies. Read deny rules also stop Edit and Write on the same path.
Are permission rules a security boundary?
Not on their own. A Bash rule matches the command text, so Bash(curl *) does not stop /usr/bin/curl or sh -c "curl …". For real isolation use sandboxing, and use a PreToolUse hook when you need custom logic.
Sources: Claude Code permissions. Claude Code Agents is an independent site, not affiliated with Anthropic.