Skip to content

Course · Claude Code · Permissions & modes

Understand the default permission prompt

Before you loosen anything, know what you are loosening from. Claude Code’s default permission mode - a permission mode is a named posture that decides which actions stop and ask you before they run - is the baseline every other setting in this chapter is defined against. A mechanical example shows its shape best. The course’s payments service (a seven-file Express and TypeScript app) exports its charge function as processPayment from src/payments/processor.ts, and src/index.ts and tests/payments.test.ts call it. Suppose you want it renamed to capturePayment across every definition, call site, and test. The task is boring on purpose: there is nothing to reason about, only the mechanics of how the agent is allowed to act.

One orientation fact first. On current Claude Code, a plain claude launch in the terminal or the VS Code extension starts in auto mode, where most actions run without asking and a background classifier blocks the unsafe ones (v2.1.283 and later; before that, auto was the starting mode only on Pro, Max, and Team plans, from v2.1.228 on macOS, Linux, and WSL and v2.1.233 on native Windows). claude -p and Agent SDK runs with no permission mode set start in manual (default) from v2.1.302, and an administrator who sets disableAutoMode returns the starting mode to default; the permission-modes reference lists the rest. Default mode is the posture where everything consequential stops and asks, and this course’s walkthroughs pin it with claude --permission-mode default so what you see matches what the pages describe. The modes are documented in the permission-modes reference; the hands-off lesson covers the auto end of the ladder. If you use the VS Code extension, its starting mode comes from the claudeCode.initialPermissionMode setting; the extension never reads project settings for the starting mode.

The exchange below is illustrative, not captured; exact prompt wording varies by client and release:

> rename processPayment to capturePayment across the codebase, updating
every call site and the tests
⎿ Read src/payments/processor.ts, src/index.ts, tests/payments.test.ts (3 files)
⏵ Edit src/payments/processor.ts
- export async function processPayment(
+ export async function capturePayment(
Apply this change? (y/n)

Notice the asymmetry. The agent read three files without asking you anything: reads inside the working directory are free in default mode. The moment it wants to change a file, it stops and waits for your approval. Edits and potentially consequential commands generally prompt, while some built-in read-only commands run without prompting.

This rename touches nine occurrences in three files, and each edit stops for approval; on a service with two dozen call sites it would be two dozen prompts. Accept-edits mode exists to remove exactly that friction. But sit with why the friction exists before you remove it. In default mode there is no path by which the agent changes your world that you did not personally wave through. That is a strong safety property, and it is the right one for unfamiliar code, anything touching money or data, or simply the first day with a new project.

Every prompt is an allow / ask / deny decision

Section titled “Every prompt is an allow / ask / deny decision”

The y/n prompt looks binary, but underneath it Claude Code consults a three-way rulebook for every action: is this allowed (run it silently), does it need an ask (prompt you), or is it denied (refuse outright)? Default mode is the cautious starting posture, not a promise that every tool call asks. The two big levers in this chapter are the two ends of that rulebook:

  • An allow rule pushes a specific safe action down to silent - “never ask me about npm test again.”
  • A deny rule pushes a specific dangerous one up to forbidden - “never let it read .env, even if I get sloppy.”

The rules are evaluated deny first, then ask, then allow; an action that matches none of them falls through to whatever the current mode does with it, which in default mode is ask. The simulator below makes that order concrete. It carries an example policy and walks a handful of commands through it - a test run, a push, a destructive delete, an edit to .env - showing which list matched and the verdict that resulted. Claude Code is one of three tools it models; the other tabs are the cross-tool payoff, and the foundations chapter on permissions has the full comparison. If the simulator does not render, the order above is the whole mechanism.

Rules live in settings.json as allow / ask / deny lists. Evaluation order: deny beats ask beats allow; anything unmatched falls through to the default (ask). Example policy:

{
  "permissions": {
    "deny":  ["Bash(rm -rf:*)", "Edit(.env)", "Read(.env)"],
    "ask":   ["Bash(git push:*)", "Bash(npm install:*)"],
    "allow": ["Bash(npm run test:*)", "Read(src/**)", "Bash(git status)"]
  }
}
The agent tries:
  1. deny list No deny rule matches.
  2. ask list No ask rule matches.
  3. allow listBash(npm run test:*) Allow rule matches - the command runs without a prompt.
Result: runs without asking

Everything else in this chapter moves actions between those three buckets, either in bulk (the modes, via Shift+Tab) or one rule at a time (allow and deny rules). You can see and edit the current rulebook anytime with /permissions. The exact rule syntax and how the buckets interact are in the permissions docs, worth a bookmark because the syntax gains options over time.

You now know the baseline: maximally cautious, every consequential action waved through by hand. That is right for the dangerous part of your day and miserable for the mechanical part. For a rename with no stakes, take the leash off the edits.