[BUG] Bash permissions in settings.json not enforced - requires custom hook workaround
Bug Report: Bash Permissions in settings.json Not Respected
Summary
The permissions.allow and permissions.deny rules for Bash commands in settings.json are not reliably enforced. Users must create custom PreToolUse hooks to achieve the permission behavior that the configuration system promises.
Environment
- Claude Code Version: Latest (check with
claude --version) - OS: macOS (Darwin 25.2.0)
- Interface: CLI (Terminal)
Steps to Reproduce
- Create
~/.claude/settings.jsonwith Bash permissions:
{
"permissions": {
"allow": [
"Bash(mkdir:*)",
"Bash(ls:*)",
"Bash(git status:*)"
],
"deny": [
"Bash(rm:*)"
]
}
}
- Start a new Claude Code session
- Ask Claude to run
mkdir -p /tmp/test - Expected: Command runs without permission prompt
- Actual: Permission prompt appears despite the allow rule
Expected Behavior
Commands matching patterns in permissions.allow should execute without prompting.
Commands matching patterns in permissions.deny should be blocked automatically.
Actual Behavior
- Allow rules are ignored for Bash commands
- Users are prompted for every Bash command regardless of configuration
- This forces users to either:
- Use
--dangerously-skip-permissions(unsafe) - Click "Allow" hundreds of times per session
- Implement custom PreToolUse hooks (workaround I had to use)
Related Issues
- #15921: VSCode Extension doesn't respect Bash/Write/Edit permissions at all
- #13340: Piped commands bypass permission allowlist
Impact
This is a high-friction issue that significantly degrades the user experience:
- Productivity loss: Users spend time approving commands that should be auto-approved
- Workaround complexity: Users must write custom Python hooks to get basic functionality
- Documentation mismatch: The settings.json documentation implies this should work
- Trust erosion: Users lose confidence in the permission system
Workaround
I created a PreToolUse hook that properly enforces the allow/deny rules:
#!/usr/bin/env python3
"""PreToolUse hook that enforces Bash permissions from settings.json."""
import json
import sys
from pathlib import Path
def load_settings():
settings_file = Path.home() / ".claude" / "settings.json"
allow_patterns = []
deny_patterns = []
if settings_file.exists():
with open(settings_file, 'r') as f:
settings = json.load(f)
permissions = settings.get("permissions", {})
for rule in permissions.get("allow", []):
if rule.startswith("Bash(") and rule.endswith(")"):
allow_patterns.append(rule[5:-1])
for rule in permissions.get("deny", []):
if rule.startswith("Bash(") and rule.endswith(")"):
deny_patterns.append(rule[5:-1])
return allow_patterns, deny_patterns
def pattern_matches(pattern, command):
command = command.strip()
if pattern.endswith(":*"):
return command.startswith(pattern[:-2])
if pattern.endswith(" *"):
return command.startswith(pattern[:-2])
return command.startswith(pattern)
def main():
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError:
sys.exit(0)
if input_data.get("tool_name") != "Bash":
sys.exit(0)
command = input_data.get("tool_input", {}).get("command", "")
allow_patterns, deny_patterns = load_settings()
# Deny takes priority
for pattern in deny_patterns:
if pattern_matches(pattern, command):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"Denied: {pattern}"
}
}))
sys.exit(0)
# Check allow
for pattern in allow_patterns:
if pattern_matches(pattern, command):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": f"Allowed: {pattern}"
}
}))
sys.exit(0)
sys.exit(0)
if __name__ == "__main__":
main()
Suggested Fix
The built-in permission system should:
- Actually enforce the allow/deny rules in settings.json for Bash commands
- Handle piped commands by checking each component (as noted in #13340)
- Work consistently across CLI and VSCode extension
- Document limitations clearly if certain patterns can't be supported
Additional Context
This user had 800+ Bash command patterns meticulously configured in settings.json, expecting them to work as documented. The discovery that none of them were being enforced was extremely frustrating and time-consuming to debug.
---
Submitted by: User via Claude Code session
Date: 2026-01-17
This issue has 10 comments on GitHub. Read the full discussion on GitHub ↗