Overview

Claude Code prompts for permission before running tools that modify files or execute shell commands. Permissions are configurable: you can require approval for every tool call, pre-approve specific commands, or allow everything. The goal is reducing friction on commands that are safe and predictable while keeping manual review on commands that are not. See claude-code for the broader workflow; this page covers the permission system specifically.

Choose a permission mode based on trust level

Claude Code operates in one of two modes, set per project in .claude/settings.json.

  • Manual (default): every tool call prompts for approval unless the tool is on the allowlist.
  • Auto: all tool calls run without prompting. Use only in fully automated pipelines with no human in the loop.
{
  "permissions": {
    "mode": "manual"
  }
}

Manual mode is the right default for interactive sessions. Auto mode is for CI jobs and scripted agents where prompts cannot be answered. Never set auto mode in a user-facing session on a production repo.

Build the allowlist from your session transcripts

The fastest way to build a good allowlist is to run a few sessions in manual mode, note which prompts fire on every turn, and pre-approve those. Repetitive read-only commands are the most common candidates.

Common allowlist entries for a Node project:

{
  "permissions": {
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run build)",
      "Bash(npx quartz build)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Bash(find . *)",
      "Read(*)"
    ]
  }
}

The pattern syntax uses glob-style matching. Bash(git diff *) matches any git diff invocation; Bash(git diff --staged) matches only that exact form. Use narrow patterns for commands with side effects and wide patterns for read-only commands.

Scope the allowlist per project, not per user

User-level allowlists (~/.claude/settings.json) apply everywhere. Project-level allowlists (.claude/settings.json) apply only to that repo. Prefer project-level for commands that are specific to the stack or toolchain.

A Bash(npx quartz build) allowlist entry makes no sense in a Rails project. Commit .claude/settings.json to the repo so every team member starts with the same allowlist.

Do not commit secrets to settings.json. Secrets belong in environment variables. See claude-code-mcp for the env pattern.

Deny dangerous patterns explicitly

The deny list blocks tool calls even when the model requests them. Use it for commands that should never run in the project context.

{
  "permissions": {
    "deny": [
      "Bash(rm -rf *)",
      "Bash(git push --force *)",
      "Bash(git reset --hard *)",
      "Bash(DROP TABLE *)"
    ]
  }
}

The deny list is a safety net for the cases where the model makes a confident mistake. It also serves as documentation: the list tells new contributors what is out of bounds.

Use per-tool scoping for MCP servers

MCP tools are scoped separately from Bash. Use allowedTools on the server config to pre-approve specific MCP tools without affecting the Bash allowlist.

{
  "mcpServers": {
    "github": {
      "allowedTools": [
        "mcp__github__pull_request_read",
        "mcp__github__list_issues"
      ]
    }
  }
}

Read-only MCP tools (list, get, read) are good allowlist candidates. Write tools (create, update, merge, delete) should stay in manual mode unless the session is scripted and reviewed.

The fewer-permission-prompts pattern

Scan a session transcript for the five most frequent permission prompts. Add those to the allowlist. Repeat after every major workflow change.

The goal is not zero prompts. The goal is zero prompts for commands that are always safe in this project, and prompts remaining for commands that need human judgment. A well-tuned allowlist means Claude asks only when it matters.

For a first pass: allowlist all Read calls, all git read commands, and your primary build command. That alone eliminates most prompts in a typical content-editing or coding session.