[FEATURE] Per-Command Permission Control for "Allow Always" Option

Resolved 💬 3 comments Opened Jan 15, 2026 by oNaiPs Closed Jan 15, 2026

Preflight Checklist

  • [x] I have searched existing enhancement requests
  • [x] This is a single feature request (not multiple requests)

Problem Statement

When Claude Code prompts for permission to run commands, it offers three options:

  1. Yes
  2. Yes, and always allow access to [directory] from this project
  3. No

For security-sensitive or destructive operations (like rm, git push --force, docker system prune), allowing permanent approval creates unnecessary risk. Developers might accidentally approve "always allow" for dangerous command patterns, enabling those commands to execute without future oversight.

Repository maintainers currently have no way to:

  1. Prevent the "always allow" option from appearing for specific commands
  2. Completely disable the "always allow" option globally

This creates a security gap where users can bypass project-level permission configurations by permanently approving operations that haven't been explicitly pre-authorized.

Proposed Solution

Add two complementary permission controls in .claude/settings.json:

1. Global Disable (Strictest Control)

{
  "permissions": {
    "disableAlwaysAllow": true,
    "allow": ["Bash(git status:*)", "Bash(yarn install:*)"],
    "deny": ["Read(.env)", "Read(secrets.json)"]
  }
}

When disableAlwaysAllow: true, the permission dialog never shows the "always allow" option. All permissions must be:

  • Pre-configured in the allow array, OR
  • Approved one-time for each execution

2. Per-Command Control (Granular Control)

{
  "permissions": {
    "allow": ["Bash(git status:*)", "Bash(yarn install:*)"],
    "deny": ["Read(.env)", "Read(secrets.json)"],
    "requireExplicitApproval": [
      "Bash(rm:*)",
      "Bash(git push:*--force*)",
      "Bash(docker system prune:*)",
      "Bash(chmod:-R*)",
      "Bash(chown:-R*)",
      "Bash(yarn remove:*)",
      "Bash(npm uninstall:*)"
    ]
  }
}

When a command matches requireExplicitApproval, the "always allow" option is hidden for just those specific commands.

User Experience Comparison

With disableAlwaysAllow: true (global):

Command: yarn build
Dialog:
1. Yes (this time only)
2. No

With requireExplicitApproval (per-command):

Command: rm temp.txt
Dialog:
1. Yes (this time only)
2. No

Command: yarn build
Dialog:
1. Yes
2. Yes, and always allow
3. No

Expected Behavior Table

| Command | Configuration | Dialog Options |
|---------|--------------|----------------|
| git status | In allow array | No prompt (pre-authorized) |
| rm -rf / | In deny array | Blocked entirely |
| rm file.txt | disableAlwaysAllow: true | "Yes (this time)" / "No" only |
| rm file.txt | In requireExplicitApproval | "Yes (this time)" / "No" only |
| yarn build | Not configured + disableAlwaysAllow: true | "Yes (this time)" / "No" only |
| yarn build | Not configured (default) | "Yes" / "Always allow" / "No" |

Alternative Solutions

  1. Severity levels: Commands could be tagged with severity levels (safe/caution/danger), with "always allow" automatically disabled for danger-level commands
  1. Time-based approvals: Replace permanent "always allow" with temporary approvals (e.g., "allow for 1 hour" or "allow for this session")
  1. Layered configuration: Support multiple settings files (e.g., .claude/settings.json and .claude/settings.local.json) where project-level settings take precedence over local preferences

Current workaround: Use the deny array to completely block dangerous commands, but this prevents legitimate one-time use cases requiring manual approval.

Priority

Medium

(While not blocking daily work, this addresses legitimate security concerns for teams and enterprises using Claude Code in production repositories)

Feature Category

Configuration

Use Case Example

Use Case 1: Strict Project Configuration (Global Disable)

Scenario: Development team with strict security policies

Configuration:

{
  "permissions": {
    "disableAlwaysAllow": true,
    "allow": [
      "Bash(git status:*)",
      "Bash(git diff:*)",
      "Bash(yarn install:*)",
      "Bash(yarn build:*)",
      "Bash(yarn test:*)"
    ],
    "deny": [
      "Read(.env)",
      "Read(secrets.json)"
    ]
  }
}

Workflow:

  1. Project maintainer configures all safe operations in allow array
  2. disableAlwaysAllow: true prevents users from permanently approving anything else
  3. Developer asks Claude: "Build the project" → runs immediately (pre-authorized)
  4. Developer asks Claude: "Clean up temp files" → shows approval dialog WITHOUT "always allow"
  5. Project configuration ensures users can only permanently use pre-approved operations

Security benefit: Users cannot bypass project-level controls by clicking "always allow" on unapproved operations.

Use Case 2: Selective Protection (Per-Command)

Scenario: Development team with specific dangerous commands to protect

Configuration:

{
  "permissions": {
    "requireExplicitApproval": [
      "Bash(rm:*)",
      "Bash(git push:*--force*)",
      "Bash(docker system prune:*)"
    ]
  }
}

Workflow:

  1. Developer asks Claude: "Clean up temp files"
  2. Claude runs: rm -rf /tmp/cache
  3. Dialog shows only: "Yes (this time)" / "No"
  4. Developer reviews and approves
  5. Next rm command requires approval again
  6. Other commands (like yarn add) can still be permanently approved normally

Security benefit: Dangerous commands require review every time, while general workflow isn't interrupted.

Additional Context

Configuration Control Levels (Weakest → Strongest):

  1. Default: Users can permanently approve anything (current behavior)
  2. Per-command control: Project flags specific dangerous commands via requireExplicitApproval
  3. Global disable: Project disables "always allow" completely via disableAlwaysAllow
  4. Full lockdown: Combine disableAlwaysAllow + comprehensive allow/deny lists

Relationship to existing permission system:

This feature extends existing controls:

  • allow array: Pre-authorize safe commands (no prompt needed)
  • deny array: Block dangerous commands entirely (cannot be run)
  • requireExplicitApproval array: NEW - Disable "always allow" for specific commands
  • disableAlwaysAllow flag: NEW - Disable "always allow" globally

Permission resolution order:

  1. Check deny → blocked if matched
  2. Check allow → execute immediately if matched
  3. Otherwise → show permission dialog
  • If disableAlwaysAllow: true → hide "always allow" option
  • If command matches requireExplicitApproval → hide "always allow" option
  • Otherwise → show all three options (Yes / Always allow / No)

Security benefits:

  • Prevents accidental or intentional bypass of project-level controls
  • Enables audit compliance (critical operations always require explicit human oversight)
  • Allows repository maintainers to enforce safety policies consistently across all contributors
  • Provides layered security: from permissive (default) to locked-down (global disable + allowlists)
  • Granular control without blocking legitimate use cases

Target users:

  • Enterprise teams with strict security policies
  • Open-source projects with multiple contributors
  • Repositories handling sensitive data or infrastructure
  • Teams requiring audit trails for all code changes
  • Organizations with compliance requirements (SOC 2, ISO 27001, etc.)

Implementation considerations:

  • disableAlwaysAllow should be repository-level config (committed to git)
  • Configuration should support layered overrides (project-level takes precedence)
  • Users should see a message explaining why "always allow" is disabled when applicable

View original on GitHub ↗

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