[Feature] Mode-specific permissions: Separate command allowlists for read-only vs normal modes

Resolved 💬 4 comments Opened Jun 13, 2025 by gwpl Closed Jan 7, 2026

(Like #1946 but with 20/20 hindsight and 🤖 Claude Code's architectural review -
turns out "read-only" meant more than we initially explained 🔍)

Problem Statement

Currently, Claude Code's permission system has a single allowlist/denylist that applies across all operational modes. This creates a fundamental limitation: users cannot specify different sets of allowed commands for different modes (e.g., plan mode vs normal mode).

This is particularly problematic because:

  1. In plan mode, users want Claude to explore and understand the codebase WITHOUT making changes
  2. Some commands are safe for exploration (ls, cat, grep) while others have side effects (rm, git commit, npm install)
  3. There's no way to allow a command in normal mode but restrict it in plan mode

Current Behavior

{
  "permissions": {
    "allow": ["Bash(ls:*)", "Bash(cat:*)", "Bash(rm:*)", "Bash(git:*)", "Read", "Write"],  // Applies to ALL modes
    "deny": ["Bash(sudo:*)"]
  }
}

In plan mode, Claude can still run rm or git commit if they're in the allowlist, which defeats the purpose of a read-only planning phase.

Proposed Solution

Introduce mode-specific permission configurations:

{
  "permissions": {
    "allow": ["Bash(ls:*)", "Bash(cat:*)", "Bash(grep:*)", "Bash(git:*)", "Bash(npm:*)", "Bash(rm:*)", "Bash(mv:*)", "Read", "Write", "Edit"],  // Normal mode
    "allowReadonly": ["Bash(ls:*)", "Bash(cat:*)", "Bash(grep:*)", "Bash(git status:*)", "Bash(git log:*)", "Bash(find:*)", "Read", "Grep", "Glob", "LS"],  // Plan mode & future read-only modes
    "deny": ["Bash(sudo:*)"]  // Always denied
  }
}

Implementation Details

  1. Backwards Compatibility: If allowReadonly is not specified, fall back to current behavior (use allow for all modes)
  1. Mode Detection:
  • Plan mode: Use allowReadonly list
  • Normal mode: Use allow list
  • Future /ask mode: Use allowReadonly list
  1. Command Matching: Support both exact matches and prefix matches:
  • "Bash(git:*)" allows all git subcommands
  • "Bash(git status:*)" allows only that specific git subcommand
  1. Precedence Rules:
  • deny list always takes precedence
  • In read-only modes: only commands in allowReadonly are permitted
  • In normal mode: only commands in allow are permitted

Use Cases

  1. Safe Exploration: Users can freely explore codebases in plan mode without risk of modifications
  2. Granular Control: Power users can fine-tune which git subcommands are available in each mode
  3. Security: Reduces risk of accidental destructive operations during planning phases

Example Configuration

{
  "permissions": {
    "allow": [
      "Bash(ls:*)", "Bash(cat:*)", "Bash(grep:*)", "Bash(find:*)", "Bash(rg:*)",  // Read operations
      "Bash(git:*)", "Bash(npm:*)", "Bash(yarn:*)", "Bash(make:*)",              // Build/version control
      "Bash(rm:*)", "Bash(mv:*)", "Bash(cp:*)", "Bash(mkdir:*)",                 // File operations
      "Bash(python:*)", "Bash(node:*)", "Bash(cargo:*)",                          // Execution
      "Read", "Write", "Edit", "MultiEdit", "Grep", "Glob", "LS"                 // Claude Code tools
    ],
    "allowReadonly": [
      "Bash(ls:*)", "Bash(cat:*)", "Bash(grep:*)", "Bash(find:*)", "Bash(rg:*)",  // Read operations only
      "Bash(git status:*)", "Bash(git log:*)", "Bash(git diff:*)", "Bash(git branch:*)", // Read-only git commands
      "Bash(npm list:*)", "Bash(yarn list:*)", "Bash(cargo tree:*)",              // Read-only package commands
      "Bash(which:*)", "Bash(pwd)", "Bash(echo:*)", "Bash(env:*)",                // Safe system commands
      "Read", "Grep", "Glob", "LS",                                               // Read-only Claude Code tools
      "WebFetch", "WebSearch", "NotebookRead"                                     // Information gathering
    ],
    "deny": ["Bash(sudo:*)", "Bash(chmod:*)", "Bash(chown:*)"]
  }
}

Related Issues

  • #1946 - Original proposal that may have appeared to focus primarily on adding an /ask command, but was actually trying to address this deeper architectural need: allowing Claude Code to work agentically with different sets of tools/commands marked as safe for read-only operations vs. normal operations

To Think About (Future Considerations)

While out of scope for this issue, this concept could extend to MCP (Model Context Protocol) integrations:

  • MCP servers could declare which of their operations are read-only
  • Users should be able to override these declarations for security reasons
  • Similar allowReadonly vs allow patterns could apply to MCP tools

However, this issue focuses specifically on the command allowlist in Claude Code's core permission system.

Benefits

  1. Safety: Clear separation between exploration and modification commands
  2. Flexibility: Users can customize their workflow based on their risk tolerance
  3. Future-proof: Enables implementation of additional read-only modes (/ask, /review, etc.)
  4. Intuitive: Aligns with user expectations that "plan mode" shouldn't modify files

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗