[FEATURE] Per-Command Permission Control for "Allow Always" Option
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:
- Yes
- Yes, and always allow access to [directory] from this project
- 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:
- Prevent the "always allow" option from appearing for specific commands
- 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
allowarray, 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
- Severity levels: Commands could be tagged with severity levels (safe/caution/danger), with "always allow" automatically disabled for danger-level commands
- Time-based approvals: Replace permanent "always allow" with temporary approvals (e.g., "allow for 1 hour" or "allow for this session")
- Layered configuration: Support multiple settings files (e.g.,
.claude/settings.jsonand.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:
- Project maintainer configures all safe operations in
allowarray disableAlwaysAllow: trueprevents users from permanently approving anything else- Developer asks Claude: "Build the project" → runs immediately (pre-authorized)
- Developer asks Claude: "Clean up temp files" → shows approval dialog WITHOUT "always allow"
- 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:
- Developer asks Claude: "Clean up temp files"
- Claude runs:
rm -rf /tmp/cache - Dialog shows only: "Yes (this time)" / "No"
- Developer reviews and approves
- Next
rmcommand requires approval again - 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):
- Default: Users can permanently approve anything (current behavior)
- Per-command control: Project flags specific dangerous commands via
requireExplicitApproval - Global disable: Project disables "always allow" completely via
disableAlwaysAllow - Full lockdown: Combine
disableAlwaysAllow+ comprehensiveallow/denylists
Relationship to existing permission system:
This feature extends existing controls:
allowarray: Pre-authorize safe commands (no prompt needed)denyarray: Block dangerous commands entirely (cannot be run)requireExplicitApprovalarray: NEW - Disable "always allow" for specific commandsdisableAlwaysAllowflag: NEW - Disable "always allow" globally
Permission resolution order:
- Check
deny→ blocked if matched - Check
allow→ execute immediately if matched - 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:
disableAlwaysAllowshould 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
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗