Claude Code keeps asking permission: allow rules that actually work
Claude Code asks before it runs most shell commands. You can pre-approve the safe, repetitive ones, such as your tests and linter, with allow rules. The catch is that the rules have precise matching behavior, and in one common setup they are silently ignored. We tested each case below in real Claude Code 2.1.278 sessions in September 2026.
The short version: write the command you want approved and put a * after the subcommand. A rule with no * matches that exact command and nothing else. And if you put allow rules in a project's .claude/settings.json, they only apply after you accept Claude Code's "trust this folder" dialog.
Where to put the rules
.claude/settings.local.json: yours only, for this project. Applies right away.~/.claude/settings.json: yours, in every project..claude/settings.json: shared with your team through git, but see the next section.
{
"permissions": {
"allow": [
"Bash(npm test)",
"Bash(npm run build *)"
]
}
}
The trust trap: allow rules that silently do nothing
Claude Code's documentation says allow rules in a project's .claude/settings.json grant capability, so it applies them only after you accept the workspace trust dialog for that folder. deny rules are not affected, since they only restrict. The dialog appears in interactive sessions only. A claude -p run never shows it, so the project's allow rules are never used there, and Claude Code prints a "this workspace has not been trusted" warning instead.
We hit exactly this. With Bash(npm test) in the project's .claude/settings.json, every command we tried in a claude -p run was refused with "This command requires approval", including npm test itself. When we moved the same rules to .claude/settings.local.json, they worked. If your rules seem to do nothing, check where they live. If you share rules through git, each teammate must accept the trust dialog once.
How matching works (tested)
With the two rules above, Bash(npm test) and Bash(npm run build *), we asked Claude to run eight commands:
| Command | Result | Why |
|---|---|---|
npm test | Allowed | Exact match |
npm test -- --silent | Refused | A rule without * matches only that exact command, so any extra argument breaks it |
npm run build | Allowed | A trailing * after a space also matches the bare command |
npm run build -- --watch | Allowed | The * stands for any arguments |
npm run lint | Refused | Not covered by any rule |
timeout 30 npm test | Allowed | Claude Code strips wrappers such as timeout, time, nice and nohup before matching |
NODE_ENV=production npm test | Allowed | Known-safe variable assignments such as NODE_ENV are ignored |
npm test && npm run lint | Refused | Each part is checked separately, and npm run lint has no rule |
The fourth row is worth a second look: the command was allowed, and then failed on its own, because we passed --watch to a script that could not take it. An allow rule decides permission only. It does not check that the command makes sense.
Where to put the star
The documentation is direct about this: put the * after the subcommand. Bash(git log *) allows only git log commands. Bash(git *) allows every git subcommand, including git push. The space before a trailing * matters too: Bash(ls *) matches ls -la but not lsof, whereas Bash(ls*) matches both.
A safe starting list
Approve the things you would run without thinking, and nothing that changes state outside your project:
{
"permissions": {
"allow": [
"Bash(npm test *)",
"Bash(npm run lint *)",
"Bash(npm run typecheck *)",
"Bash(npm run build *)"
]
}
}
Claude Code already runs a built-in set of read-only commands without asking, such as ls, cat, echo, pwd, head, tail, grep, wc and diff, so you do not need rules for those. Use your own script names in place of the examples.
Allow rules that are riskier than they look
- Runner commands. Claude Code does not strip
npx,docker exec,direnv execor similar. A rule such asBash(devbox run *)matches whatever followsrun, includingdevbox run rm -rf .. Write a specific rule that includes the exact inner command instead. findwith-execor-deleteis not covered by aBash(find *)rule. It still prompts.- Other variable prefixes. An allow rule ignores a known-safe assignment like
NODE_ENV=test, but not other variables, soFOO=bar npm testmay still prompt. - Deny beats allow. If a deny rule matches, the command is blocked whatever your allow rules say.
Allow rules are not a safety net
Allow rules remove prompts. They do not stop anything. For the commands you never want run, use deny rules, and note that even a deny rule can be sidestepped by a different spelling of the command. Both halves are covered in how to block git push, rm -rf and .env reads. Hooks and deny rules are not held back by the trust dialog. They applied in our claude -p tests, where the dialog never appears, which is what you want from a guardrail.
Want it set up for you? The Safe setup pack ships deny rules and hooks that are tested against 100 dangerous commands and 65 harmless ones.