Every "Allow this command?" prompt in Claude Code is a small interruption. Click "always allow" enough times and you end up with a settings.json nobody has read. Here are three presets I keep in .claude/settings.json, from most to least careful, and the one thing none of them can do.
Rules are matched in this order: deny beats ask, and ask beats allow. Anything not listed falls back to defaultMode.
Read-only git is free. Commits, pushes and installs need a confirmation, and network tools are off.
{
"permissions": {
"defaultMode": "default",
"allow": [
"Bash(git status)",
"Bash(git diff:*)",
"Bash(git log:*)"
],
"ask": [
"Bash(git commit:*)",
"Bash(git push:*)",
"Bash(npm install:*)",
"Bash(pnpm add:*)",
"Bash(yarn add:*)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Bash(curl:*)",
"Bash(wget:*)",
"WebFetch"
]
}
}
Edits are accepted automatically (acceptEdits). Lint, tests and type checks run without asking, and anything that leaves your machine or changes dependencies still asks.
{
"permissions": {
"defaultMode": "acceptEdits",
"allow": [
"Bash(git status)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git add:*)",
"Bash(npm run lint:*)",
"Bash(npm run test:*)",
"Bash(npm test:*)",
"Bash(npx tsc --noEmit:*)"
],
"ask": [
"Bash(git push:*)",
"Bash(npm install:*)",
"Bash(pnpm add:*)",
"Bash(yarn add:*)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
All of git and npm are allowed. Use it only where a bad command costs you a branch, not a production database.
{
"permissions": {
"defaultMode": "acceptEdits",
"allow": [
"Bash(git:*)",
"Bash(npm:*)",
"Bash(npx:*)",
"Bash(pnpm:*)",
"Bash(node:*)",
"WebFetch",
"WebSearch"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
Look at the autonomous preset again: Bash(git:*) allows git push --force and git reset --hard too. Permission rules match a command by its prefix. They can't tell a safe git push from a destructive one.
There's a second gap. Read(./.env) blocks Claude's Read tool, but it isn't a rule about the shell. A cat .env through Bash is a different tool and needs its own handling.
That's what hooks are for. A PreToolUse hook is a script that sees the full command before it runs and can block it with exit 2, so it can reject git push --force while allowing a normal push, or refuse any command that touches .env. Two free ones (one blocks destructive commands, tested against bypasses like git push origin +main; one stops secrets from being written into files) are here: https://github.com/kailucho/claude-code-hooks-seguridad (free, MIT).
Presets for convenience, hooks for safety. You want both.
The three presets, the hooks, 6 skills (/commit, /pr, /review, /tests, /plan, /debug) and CLAUDE.md templates for React, Node and GraphQL come together in the Starter Kit for Claude Code (US$9, unofficial): https://luijhy.gumroad.com/l/starter-kit-for-claude-code