Curriculum › Claude Code · 3.1% of the exam

Permissions and tool access

What you’ll be able to do

  • Match each permission mode to what it actually restricts
  • Apply the settings.json scope hierarchy to a permission conflict
  • Recognise when bypassing permissions is appropriate versus reckless
  • Scope allow/deny rules to a tool pattern rather than a blanket setting

What you need to know

Four modes, one axis

Permission mode controls a single question: does a human approve a tool call before it runs? It says nothing about which tools exist or what they're allowed to touch — that's a separate axis, covered next.

  • default — every tool call outside a small safe set prompts for approval. The starting point for interactive work.
  • acceptEdits — file edits are auto-approved; other tool categories (shell commands, network calls) still prompt. Useful mid-session once you trust the direction of the work.
  • plan — read-only by construction. The model can explore and propose but cannot execute a mutating call, full stop. Not a stricter version of asking permission — there is no permission to grant.
  • bypassPermissions — nothing prompts. Every call the model requests runs immediately.

The exam distinction that gets tested: plan mode isn't "default but pickier." Default still lets a mutating call through once approved. Plan mode has no path to execution at all — the restriction is structural, not a stricter policy.

The scope hierarchy — same shape as CLAUDE.md, different file

Permission settings in settings.json layer exactly like the CLAUDE.md hierarchy from lesson 3.1: enterprise-managed, then project, then project-local, then user, each narrower scope able to override the broader one it sits inside. The mistake is also the same one: a developer sets a permissive personal override, it works for them, and a teammate who clones the repo gets the strict project default with no idea a looser option existed. Team-wide policy belongs in the committed project settings file. A personal convenience override belongs in the local, untracked one.

Allow, ask, deny — and the trap in “deny”

settings.json — scoped, not blanket
{ "permissions": { "allow": ["Read", "Grep", "Glob"], "ask": ["Edit", "Write"], "deny": ["Bash(curl:*)", "Bash(rm -rf:*)"] } }

Rules match a tool and, for tools like Bash, a command pattern — not just a tool name. A deny rule written as "Bash" blocks every shell command including harmless ones, which breaks the workflow without adding safety anywhere near what actually mattered. The useful version denies the specific dangerous pattern and leaves the rest asking or allowed. Overly broad deny rules are a design smell in exactly the same way an overly broad allow rule is.

Bypassing permissions on purpose

A sandboxed container with no network access and disposable state is a legitimate place to run with permissions bypassed — there's nothing to protect and no human available to ask, which is the same headless reasoning from lesson 3.1. A developer's laptop with real credentials and an open network connection is not that place, no matter how much faster it feels. The setting is the same; the blast radius if the model does something unintended is not.

Key concept

Permission mode decides who approves a tool call, not whether the tool call is allowed to exist. Plan mode can’t execute by design; bypassPermissions removes the human from every loop, appropriate only where the environment itself is the safety boundary.

Practice scenario

ScenarioA teammate asks why their Claude Code session keeps prompting for approval on `git commit` even though they set `acceptEdits` mode locally, while the shared project settings still show `default`.
Work it through, then open this

acceptEdits only auto-approves file edits — it says nothing about other tool categories like shell commands, so a commit still prompts under it. Separately, their local override doesn’t change what a teammate without that override experiences, because scope layers narrow-to-broad the same way CLAUDE.md does. If the team wants commits to stop prompting, that’s a project-level allow rule for the specific git pattern, not a personal mode change.

Build exercise — Write a permission policy that survives a teammate’s clone

Beginner · 20 min

What you’ll learn

  • Separating “who approves” from “what’s allowed”
  • Scoping a rule to a command pattern instead of a whole tool
  1. List the tool calls your current project work actually needs unattended (reads, specific safe commands) versus the ones that should always prompt a human.

    • Why: Permission mode is a single global setting; the allow/ask/deny list is where the real per-tool decision lives.
    • You should see: A short “always fine” list and a much shorter “never without asking” list.
  2. Write allow rules scoped to specific command patterns for the safe list, not to whole tool names.

    • Why: A rule scoped to the tool name blocks or allows everything that tool can do, which is rarely what you meant.
    • You should see: Rules that name a command pattern, e.g. Bash(npm test:*), not just Bash.
  3. Put the policy in the committed project settings file, not your personal local one, and have a teammate clone the repo to confirm they inherit it.

    • Why: This is the same mistake as an uncommitted CLAUDE.md — a policy that only exists for you isn’t a team policy.
    • You should see: The teammate’s fresh clone prompts exactly where yours doesn’t, without them setting anything.

Exam traps

Treating plan mode as a lesser version of default mode

Default can still execute once approved. Plan mode has no execution path at all — it’s structural, not a stricter policy.

Using bypassPermissions to make a CI job stop asking

Reach for it only where the environment itself is the safety boundary — a disposable sandbox, not a machine with live credentials.

Putting a personal permission override in the committed project file

Team policy is project-scoped and committed. A personal convenience setting is local and untracked, exactly like the CLAUDE.md hierarchy.

Writing a deny rule broad enough to block the tool everywhere

Scope the rule to the dangerous command pattern. A blanket deny on a whole tool breaks the workflow without adding proportionate safety.

Sources

Quick check

A team wants Claude Code to refuse to suggest any code that writes directly to the production database — permanently, across all sessions. Which component?