# Designing allow, deny and hooks is becoming the new code review

> Source: <https://dev.to/aicoding-guide/designing-allow-deny-and-hooks-is-becoming-the-new-code-review-3epg>
> Published: 2026-11-12 19:17:08+00:00

*Originally published at [https://aicoding-guide.com](https://aicoding-guide.com/en/posts/essay-permissions-are-code-review/).*

On a team that uses coding agents daily, **the highest-leverage thing to review shifts from the code itself to the design of allow, deny and hooks.** Reading diffs afterwards does not go away. But a layer appears in front of it, and that layer stops more incidents than the reading does.

The argument rests on a line the tools' own documentation draws, not on a hunch.

**Key point**

The claim

- The docs separate instruction from enforcement, and CLAUDE.md sits on the instruction side
- Permission rules and hooks are the executable form of the review checklist items a machine can decide
- But permissions never review correctness — this is a layer added in front of review, not a replacement

The Claude Code memory page settles the question about organization-wide CLAUDE.md in one sentence:

Settings rules are enforced by the client regardless of what Claude decides to do. CLAUDE.md instructions shape Claude's behavior but are not a hard enforcement layer.

The same page names where to go when you need certainty:

Claude treats them as context, not enforced configuration. To block an action regardless of what Claude decides, use a PreToolUse hook instead.

A document holding your project's rules is something that gets **read, and followed on a best-effort basis**. Its content arrives as a user message after the system prompt, not as part of it. No amount of work sharpening its prose changes which side of the line it sits on.

That line maps directly onto review. Writing "please don't force push" in a review comment and writing it into `permissions.deny` differ in strength. The first may need writing again next week; the second is finished when you write it.

A review checklist mixes two kinds of item: the ones a person must read to judge — design intent, naming, whether the abstraction holds — and the ones a machine can decide, such as "did this touch production config" or "did a credential reach the logs."

The second kind can be written as a rule. And when you do, the docs guarantee different strengths.

| Where it lives | What the docs say | Strength | 
|---|---|---|
| `permissions.deny` | "Blocks before the classifier is consulted. Neither the classifier nor user intent can override it" | Before the classifier; intent cannot reach it | 
| `permissions.ask` | "Always prompts for a command that matches a content-scoped rule like the recipe above. The classifier cannot auto-approve a matching action" | Always reaches a human | 
| Critical-path removals | "Recognized `rm` and`rmdir` critical-path removals, which no allow rule or`PreToolUse` hook`\"allow\"` approves" | Neither a rule nor a hook can approve it | 
| PreToolUse hook | "To block an action regardless of what Claude decides" | Runs without going through Claude's judgement | 
| CLAUDE.md | "not a hard enforcement layer" | Instruction | 

The top four rows are checklist items you can express as configuration instead of comments. For anything in `deny`, the probability of missing it in review is not reduced — it leaves the realm of probability.

For the mechanics, see [Claude Code permissions in settings.json and settings.local.json](https://aicoding-guide.com/en/posts/claude-code-settings-json-permissions/) and [Claude Code permissions by example](https://aicoding-guide.com/en/posts/claude-code-permissions-examples/). For the hook side and where it stops being reliable, see [Block rm -rf with a Claude Code PreToolUse hook, and know its limits](https://aicoding-guide.com/en/posts/claude-code-hooks-block-rm/).

"Designing permissions" covers three distinct things.

`permissions`; in Codex it is named profiles, `[permissions.<name>]`, with `filesystem`, `workspace_roots` and the `network` keys beneath them.`:read-only`, `:workspace` and `:danger-full-access` as built-in profiles, which `extends` can narrow. For how approvals and sandboxing pair up, see Three layers exist because one leaks. Claude Code's auto mode suspends broad rules the moment you enter it:

By default, narrow Bash and PowerShell allow rules such as `Bash(npm test)` stay in effect in auto mode. Claude Code resolves them before the classifier runs, unless the command carries per-command allowed domains. Claude Code suspends only the broad rules that grant arbitrary code execution, such as `Bash(*)` or wildcarded interpreters, together with every rule that names `Monitor`, because Monitor commands run through the shell.

So a blanket `Bash(*)` buys nothing in auto mode. Keeping the grain fine is not style advice; it is a precondition for the rule working at all. For how the modes differ, see [Claude Code starts in auto mode by default](https://aicoding-guide.com/en/posts/claude-code-default-mode/).

Four conditions make this claim fail.

**Permissions never review correctness.** This is the hard limit. `deny` stops writes to places that are off-limits; it says nothing about whether the logic written in an allowed place is right. Algorithm choice, boundary conditions, naming and design coherence stay with human readers. Permission design is not a substitute for review — it carves out the part review no longer has to carry.

**It does not pay off where nothing is irreversible.** On a local library with no deploys and no outbound calls, the design cost is pure prepayment. The argument holds where production, an external service, or credentials are within reach.

**Rules cannot express intent.** "Don't touch this directory, except during a migration" resists being written as a rule. Claude Code's classifier does let user intent override blocks, but within tight bounds:

`hard_deny` rules block unconditionally. User intent and `allow` exceptions don't apply.

Intent only reaches `soft_deny`, and even there the required specificity is stated: "General requests don't count as explicit intent. Asking Claude to 'clean up the repo' doesn't authorize force-pushing, but asking Claude to 'force-push this branch' does." Judgement that a rule cannot capture goes back to an in-the-moment prompt.

**The configuration itself becomes something to review.** A permissions file grows and turns into another thing that is hard to read. Review moves; it does not disappear.

Given those limits, the list that actually earns its place is short.

`deny`.` rm -rf` could reach. That is enough for the layer intent cannot override.`ask`.` Bash(*)`, the question is not what to allow but which permission mode you are in.
Together that is the exercise of pulling out of your review checklist the items you keep writing every week. Whatever you pull out is reading time returned to design and correctness.

`permissions.deny`,
