Search "Claude Code permissions" and half the results teach you to turn them off. --dangerously-skip-permissions, the YOLO flag, an allow-list a mile long. It feels productive. It is the wrong instinct, and this post is about the right one.
Why skipping Claude Code permissions is the wrong fix
Skipping permissions removes the prompt without adding any judgment. That is the whole problem in one sentence. The prompt was never the point. The point was the decision behind it: is this action safe to run right now? Silence the prompt and the decision does not go somewhere better. It goes nowhere. A bad rm -rf or a force-push to shared history now runs with nothing between the agent and your repo.
The friction is real, so people are right to hate it. Approving the same harmless ls for the fiftieth time trains you to click yes on everything, which is exactly how the one dangerous call slips through. But the answer is not less judgment. It is judgment that lives in code, so you stop being the bottleneck without becoming defenseless.
The real permission model: allow, deny, or ask
Every tool call in Claude Code passes through a PreToolUse step before it runs, and a hook matched to that tool can return one of three verdicts: allow, deny, or ask. That is the model. Permission is not a static allow-list you maintain by hand. It is a policy question your code answers, per call, with the actual command in front of it.
You wire a hook to a tool by matcher. Match on Bash and your script sees every shell command before it executes. Match on Edit|Write|MultiEdit|NotebookEdit and it sees every file the agent is about to change. The hook reads the tool input from stdin, decides, and prints its verdict. To deny, it emits JSON with hookSpecificOutput.permissionDecision set to "deny", and Claude Code refuses the call. This is the machinery. If you want the full mechanics, we wrote them up in Claude Code hooks.
Two opposite decisions we shipped, and why
In the kitstarter kit we run two PreToolUse hooks that make deliberately opposite calls. One hard-blocks. One only warns. The split is the whole lesson, so here is each one and the reasoning behind it.
The build lock: clarity.mjs denies edits until the goal is confirmed
Our clarity hook matches Edit|Write|MultiEdit|NotebookEdit and, while a vague build request is still unconfirmed, returns permissionDecision: "deny" on every one. The agent physically cannot write code before the goal is agreed. It is a real lock, enforced by the tool, not a polite line in a markdown file the model can skim past and ignore.
The deny reason it prints is the coaching, verbatim: "the goal isn't confirmed yet, so building is locked. Finish the clarity round: multiple-choice questions one at a time, reflect the goal back, get an explicit yes from the user. The lock lifts on their approval." Planning files stay writable during the round, because writing CLAUDE.md or a design note is how the agent earns its way out of the lock. Building code is what is blocked, not thinking on paper. This pairs with the commands that run the clarity round itself.
Why block here? Because the cost of being wrong is high and the friction is low. An agent that builds the wrong feature for twenty minutes is expensive to unwind. Asking one clarifying question first costs seconds. When one side of the ledger is that lopsided, you enforce it in code and never think about it again.
The warning: safety-guard.mjs flags danger but never blocks
Our safety hook matches Bash and watches for the genuinely destructive shell commands: recursive deletes of a root or home path, force-pushes, hard resets, dropping a database. When it sees one it prints a warning and then, deliberately, gets out of the way. It calls process.exit(0) with the comment "warn only, never block."
The reasoning is written into the file's own header: it warns and does not block because "a kit that hard-blocks your commands gets uninstalled." That is not a compromise, it is the correct call. Shell commands are where an agent does real, varied, legitimate work. Hard-block them and every false positive is a wall between the developer and their own machine. The warning does the useful part, "did you mean this, and did you checkpoint first?", without ever taking the keys away.
The rule: block where being wrong is expensive and the friction is low
That is the whole framework, and it is why the two hooks point opposite directions. Blocking file edits before a goal is confirmed is cheap to comply with and saves you from expensive rework, so it is a hard deny. Blocking shell commands is expensive to comply with and would fire on legitimate work constantly, so it is a warning. Same event, opposite verdict, chosen by the cost of being wrong against the friction of being right.
Both hooks also fail open. Any error, and they stay silent and let the call through. A coaching feature that crashes your session is worse than no feature, so if the hook throws, it allows. That is a real design principle, not an afterthought: the safety net is never allowed to become the thing that breaks you.
Does deciding permissions this way actually pay off?
In our clean-room benchmark, yes, with honest losses. The clarity lock made the kit ask before building on 5 of 5 tasks, enforced every run, against 2 of 5 for vanilla Claude Code, where asking was mood-dependent. Output ran about 40 percent leaner for the same result. That is the deterministic deny earning its place.
The losses are real and we do not hide them, it is a house rule. On an already-clear todo-app task the kit was roughly twice as slow and about 75 percent pricier for near-identical output, because the clarity round is pure overhead when the prompt was clear to begin with. Across the suite the hook context tokens added about a 25 percent average cost premium. A permission policy is not free. It is a trade, and the trade only wins when the thing you are guarding actually costs more than the guard.
Permissions decided in code, not skipped
kitstarter ships the PreToolUse hooks, the clarity lock, and the safety warnings, tuned so your agent asks before it builds and stays out of your way otherwise. For Claude Code, Codex, and Antigravity.
