.claude/settings.json decides how Claude Code behaves in a project — what runs without asking, what is always blocked, and what happens automatically around each action.
permissions: allow, deny, ask
Three arrays of tool-pattern rules:
"permissions": {
"allow": ["Bash(npm run test:*)", "Read(./src/**)"],
"ask": ["Bash(git push:*)"],
"deny": ["Read(./.env)", "Bash(rm -rf:*)"]
}
- allow — run without prompting. Keep patterns narrow.
- ask — prompt every time. Right for irreversible actions.
- deny — never allowed. deny always wins over allow.
Put secret paths and destructive commands in deny and you have a hard floor no allow rule can override.
hooks: run commands on events
Hooks fire shell commands on lifecycle events. A PostToolUse hook that formats after every edit:
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [ { "type": "command", "command": "npx prettier --write \"$CLAUDE_FILE_PATHS\"" } ]
}
]
}
env and model
env injects environment variables into the session; model overrides which model this project uses. Both are optional — an empty {} is a valid settings file.
Where it lives
Shared project rules go in .claude/settings.json; personal overrides in .claude/settings.local.json (git-ignored); global defaults in ~/.claude/settings.json.
Security notes
denybeatsallow— lean on it for secrets and destructive commands.- Do not store API keys here; reference them via
envor a separate secrets file. - Hook commands run automatically with your privileges — read them before trusting a shared config.
Open the Claude Code settings template and ask the workspace to "deny access to the .env file" or "add a hook that runs prettier after edits", then diff the result.