settings.json: allow, ask, deny
A mode decides how much you are asked in general. Rules decide the specific answers, in a file, once.
Every time you choose allow this kind of thing from now on at a prompt, a rule gets written. Writing them yourself is the same thing without the clicking, and it can be committed so the whole team gets it.
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git commit *)"
],
"deny": [
"Bash(git push *)"
]
}
}Three lists exist: allow never asks, ask always asks even in a permissive mode, and deny refuses outright. The docs give the order plainly: rules are evaluated deny, then ask, then allow, and the first match decides. A more specific rule does not jump the queue, which is what makes a deny rule worth writing.
How a rule matches
The pattern is the tool name and, in brackets, what it may do.
| Rule | Matches | Does not match |
|---|---|---|
Bash(npm run build) | Exactly that command | npm run build --watch |
Bash(npm run *) | npm run build, npm run test | npm install |
Read(./.env) | Reading that file here | A .env in a parent directory |
WebFetch(domain:example.com) | Fetches to that domain | Any other host |
The gap worth knowing about is that these are patterns over the command text, not a boundary the operating system enforces, which is the next lesson. Bash(git push *) in the deny list stops git push origin main. It does not stop git -C . push origin main, because that is a different string. The documentation says so plainly, and the lesson to take from it is that rules are a good fence and not a wall.
Where the file goes
| File | Scope |
|---|---|
.claude/settings.json | The project. Committed, so everyone gets it |
.claude/settings.local.json | You, in this project. Gitignored |
~/.claude/settings.json | You, everywhere |
| Managed settings | Set by an organisation, and not overridable |
A sensible project file is short: allow the commands your team runs a hundred times a day, deny the two or three that should never happen without a person, and leave everything else to the mode.
Bash(git push *) and Read(**/.env) cost nothing and remove a whole class of bad afternoon.- Add an allow rule for your test command and notice the prompts stop.
- Add a deny rule for something, then ask Claude to do it and read what happens.
Little by little, you're building something great.