Build your own Claude Code
The permission engine·5 min

Why agents need permission

0 / 2

Through Chapter 05 your agent reads, writes, edits, and runs bash — for real, in a sandbox. That is exactly the problem. A loop that turns a model's tool_use into side effects, with nothing between the decision and the disk, is a foot-gun: one confident-but-wrong turn and it rm -rfs the working tree or overwrites .git. Permissions are the brakes. This chapter builds the engine that decides, for every single tool call, whether it allows, asks, or denys — before the side effect happens.

deny → ask → allow → mode default

Every call is checked against the rules in settings.json in a fixed order, and the first match wins:

  1. deny — if any deny rule matches, the call is blocked. Nothing later can override it.
  2. ask — if any ask rule matches, the user is prompted.
  3. allow — if any allow rule matches, the call runs without a prompt.
  4. mode default — no rule matched, so the active permission mode decides.

The order is not alphabetical and it is not configurable — it is the safety contract. deny is first precisely so that a deny is unconditional: a rule you wrote to stop Bash(rm *) can never be undone by a broad allow somewhere else in the file.

One call, four gates
tool call ─▶ deny match?  ──yes─▶ DENY
              │ no
              ▼
            ask match?   ──yes─▶ ASK
              │ no
              ▼
            allow match? ──yes─▶ ALLOW
              │ no
              ▼
            mode default ───────▶ allow | ask | deny

The mode default is the fallthrough when no rule matched. There are four modes:

  • default — auto-allow read-only tools, ask before any write or bash side effect.
  • acceptEdits — also auto-allow in-tree edits (Write/Edit), still ask for the rest.
  • plan — read-only: every side-effecting tool is denied until you leave plan mode.
  • bypassPermissions — allow everything (the escape hatch; use with care).

You'll build the rule matcher (step 02), then decide (step 03) which layers the mode default under the rules, then protected paths (step 04), then wire it into the loop (step 05).

Knowledge check

A Bash(rm *) call matches both an allow rule and a deny rule in settings.json. What is the decision?