What it is and why it matters
In Manual mode, Claude Code asks your permission before most actions. At first you read every question. After a while you hit yes without reading, and at that point the permission no longer protects you.
The “you handle it” button is the opposite: you decide once, with a cool head, what Claude can do on its own and what it can't. Free inside the project: editing files, running the tests, starting the server. Blocked on three things that are hard to undo: deleting, publishing, touching the real database.
You need two pieces: a permission mode, which decides how much Claude asks you, and a rules file in the project, which says what always goes through, what always asks and what's forbidden.
1. The mode
I work in accept edits: Claude edits the project's files without asking, and everything else goes through the rules below. It also accepts a few basic file commands on its own, like mkdir, mv, cp and rm, but only inside the project folder.
To turn it on, press Shift+Tab until you read “accept edits on” under the prompt. In recent versions the session already starts in auto mode, where a second model checks the actions in your place: the first Shift+Tab takes you to Manual, the second to accept edits. Or you pick it at startup, from the terminal:
claude --permission-mode acceptEdits
Before you give Claude a job, always look at that line. Auto is handy and blocks the risky actions, but the documentation itself says it doesn't guarantee safety. With accept edits you know exactly what goes through on its own, and the rules apply in both.
2. The rules, set once
Create the .claude/settings.json file in the project folder. If it already exists, don't overwrite it: add the “permissions” part to what's there. Mine looks like this:
{
"permissions": {
"defaultMode": "acceptEdits",
"allow": [
"Bash(npm test *)",
"Bash(npm run test *)",
"Bash(npm run dev *)",
"Bash(npm run lint *)"
],
"ask": [
"Bash(git push *)"
],
"deny": [
"Bash(rm *)",
"Bash(git push --force *)",
"Bash(git push -f *)",
"Bash(npm publish *)",
"Bash(vercel *)",
"Bash(npx vercel *)",
"Bash(supabase db push *)",
"Bash(psql *)",
"Read(./.env.production)"
]
}
}Put your own commands in place of mine (the tests, the server, the deploy, the client for your real database). Careful: in accept edits even rm inside the project would go through on its own, that's why it's in the deny list.
What each part does
- defaultMode: the mode the session starts in, in the terminal. You start in accept edits even if the default would be auto. The --permission-mode flag wins over the file.
- allow: the commands Claude runs without asking: tests, lint, development server. Bash(npm test *) covers npm test on its own and with any option after it.
- ask: the commands you want a question about every time, in any mode, auto included. git push sends the code online: I want to see it first.
- deny: what must never happen: deleting files, the forced push, publishing, the deploy, the real database. vercel and npx vercel are two lines because the rule on vercel doesn't see the command with npx in front.
- Read(./.env.production): Claude doesn't read the file with the production keys, neither with its own tools nor with cat, and it can't edit it.
You don't have to write the file by hand. You can have Claude adapt it:
Read this project's package.json and scripts. Then adapt this .claude/settings.json file: in allow put the real commands for the tests, the lint and the development server; in deny the commands that delete files, publish the app, deploy or touch the production database. Don't put anything in allow that deletes or sends something online. Show me the file before you save it. [paste the file here]
When Claude saves the file it asks your permission even in accept edits: that's normal, the .claude folder is protected.
3. Three things to know
Deny always wins
Claude Code checks the rules in this order: deny first, then ask, then allow. The first one that matches decides. An allow doesn't open a hole in a deny, not even if it's more precise. And deny rules apply in every mode.
In my file: git push --force is both in ask and in deny, and deny wins. git push origin main --force, on the other hand, isn't seen by the deny, because it doesn't start with “git push --force”. It does start with “git push”, though, and the ask puts the question to you. That's why I keep both lines.
The rules read the command's text
The rules read the text of the command: bash -c 'rm -rf build' or /bin/rm slip under “Bash(rm *)”. The rule doesn't recognize them, and at that point the mode and the other rules decide.
For a real guard, use a PreToolUse hook (the guard from TIPS 05) or the sandbox (/sandbox). The hook reads the whole text of every command before it runs, even inside bash -c. With the sandbox, it's the operating system that limits what commands write outside the project and which sites they connect to. Inside the project, though, it lets them write: an rm there is stopped only by the hook.
The mode with no checks
Use the mode with no checks at all (--dangerously-skip-permissions) only inside a container or a virtual machine, never on the computer with your files. The documentation, too, recommends it only in isolated environments. If you want fewer questions on your own computer, the way to go is accept edits with the rules, or auto.
4. Check
Type /permissions inside Claude Code: you see all the active rules and which file each one comes from. If a rule isn't there, usually the file is in the wrong place or the JSON has a mistake, like a missing comma.
Then run a real test. Write to Claude:
Create an empty file called permissions-test.txt. Then try these three commands, one at a time, and for each one tell me what happened: blocked by a rule, you asked me for permission, or it ran on its own. - npm test (or this project's test command) - rm permissions-test.txt - git push --dry-run If a command gets blocked, don't look for other ways to run it.
This is what you expect: tests run on their own, rm blocked, git push with the question (with --dry-run it sends nothing online). You delete the test file yourself afterwards: Claude can't.
Common mistakes
- Copying the file without changing the commands: if your tests don't run with npm test, the allow does nothing and Claude asks you every time. Put in your project's real commands.
- Working in accept edits without rm in the deny list: inside the project rm goes through on its own. Keep it in deny and delete the files you want gone yourself.
- Believing the deny covers every form of the command: bash -c or /bin/rm get through. For a real guard, add the hook or the sandbox.
- Relying on instructions in chat, like “don't delete anything”: they steer Claude but they don't block anything. What must never happen goes in deny.
- Not checking which mode you're in: the session can start in auto without you choosing it. Before a long job, read the line under the prompt.
Checklist
- Mode chosen and checked under the prompt
- .claude/settings.json with your project's commands
- rm, deploy, publish and the real database in deny
- git push in ask
- Rules checked with /permissions
- Test done: tests run freely, rm blocked, push asks
- No --dangerously-skip-permissions on your computer
Frequently asked questions
Do I have to redo it in every project?
The file in the project applies to that project, and if you save it in git, also to whoever works on it with you. The rules you want everywhere, like the deny on rm, go in ~/.claude/settings.json, in your user folder. A deny blocks even if another file has an allow for the same command.
I use Claude Code in VS Code: does the same apply?
The rules, yes. The starting mode, no: the extension doesn't read defaultMode from the project file. You pick it from the selector at the bottom of the prompt box, where accept edits is called “Edit automatically”.
Which is better, accept edits or auto?
Accept edits if you want to know exactly what goes through on its own. Auto for long jobs without questions, knowing that the second model is a judgment call, not a guarantee. In both, the rules stay active and the ask still asks you. If auto doesn't show up with Shift+Tab, your account, your model or your organization doesn't allow it.