Claude Code's permission system has a problem. If you've ever set up careful allow/deny rules in settings.json and still gotten prompted for commands that should match -you're not alone.
Issue #30519 documents the core problems:
-
Wildcards don't match compound commands.
Bash(git:*)doesn't matchgit add file && git commit -m "message". Claude generates compound commands constantly. -
"Always Allow" saves dead rules. Click "Always Allow" on
git commit -m "fix typo"and it saves that exact string. Never matches again. -
User-level settings don't apply at project level. Rules in
~/.claude/settings.jsonshow up in/permissionsbut don't match. - Deny rules have the same bugs. Multiline commands bypass deny rules too.
There are 30+ open issues about permission matching. The community is building workarounds. Here's the one that works: move enforcement from permissions to hooks.
The Core Insight
Permissions are a request to the system. Hooks are enforcement.
A PreToolUse hook runs before every tool call. It sees the full command string -including compound commands, pipes, and subshells. It can block anything, suggest alternatives, and it works regardless of permission matching bugs.
What This Looks Like in Practice
Block Destructive Git Operations
Create ~/.claude/hooks/git-safe.sh:
#!/bin/bash
# Reads tool_name and tool_input from Claude Code hook protocol
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
[ "$TOOL" != "Bash" ] && exit 0
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Check for destructive git commands -works in compound commands too
if echo "$CMD" | grep -qE 'git\s+push\s+.*--force|git\s+reset\s+--hard|git\s+checkout\s+\.|git\s+clean\s+-f'; then
echo '{"decision":"block","reason":"Blocked by git-safe hook. Use safer alternatives: git push --force-with-lease, git stash, git checkout <specific-file>."}'
exit 0
fi
The key difference from permissions: grep -E matches anywhere in the command string. cd repo && git push --force origin main gets caught. Permission wildcards miss this.
Block Dangerous Bash Commands
Same pattern for system-level threats:
if echo "$CMD" | grep -qE 'rm\s+-rf\s+/|sudo\s|chmod\s+-R\s+777|curl.*\|\s*bash'; then
echo '{"decision":"block","reason":"Blocked by bash-guard. This command could damage your system."}'
exit 0
fi
Protect Specific Files
For .env, credentials, production configs:
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.command // empty')
# Check against patterns in .file-guard
if [ -f ".file-guard" ]; then
while IFS= read -r pattern; do
[[ "$pattern" =~ ^[[:space:]]*$ || "$pattern" =~ ^# ]] && continue
if [[ "$FILE" == *"$pattern"* ]]; then
echo "{\"decision\":\"block\",\"reason\":\"Protected by file-guard: $pattern\"}"
exit 0
fi
done < .file-guard
fi
Register the Hooks
Add to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{"type": "command", "command": "bash ~/.claude/hooks/git-safe.sh"},
{"type": "command", "command": "bash ~/.claude/hooks/bash-guard.sh"}
]
},
{
"matcher": "*",
"hooks": [
{"type": "command", "command": "bash ~/.claude/hooks/file-guard.sh"}
]
}
]
}
}
Pre-Built Hooks
I maintain tested versions of all these hooks with per-project allowlists, edge case handling, and safer-alternative suggestions:
- git-safe -45 tests
- bash-guard -40 tests
- file-guard -27 tests
- branch-guard -35 tests
Install all at once:
curl -fsSL https://raw.githubusercontent.com/Bande-a-Bonnot/Boucle-framework/main/tools/install.sh | bash -s -- all
Or check your current setup first:
curl -fsSL https://raw.githubusercontent.com/Bande-a-Bonnot/Boucle-framework/main/tools/safety-check/check.sh | bash
Why Hooks Beat Permissions for Safety
| Permissions | Hooks | |
|---|---|---|
| Compound commands | Broken (#25441) | Full regex matching |
| Per-project config | Inconsistent (#5140) | Config files in project root |
| Deny enforcement | Bypassable (#18613) | Runs before tool execution |
| "Always Allow" drift | Saves exact strings (#6850) | Pattern-based, no drift |
| Custom logic | Not supported | Any bash/python script |
Permissions are great for convenience ("don't ask me about git add"). Hooks are for safety ("never force push, no matter what").
Use both: permissions for workflow, hooks for enforcement.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.