You know the drill. Claude Code wants to read a file. You click Yes. It wants to grep something. Yes. List a directory. Yes. Check git status. Yes. Forty-seven times a day, you're a human rubber stamp.
Here's a hook that auto-approves read-only operations. In my testing, it cuts daily approval prompts by roughly 60%.
The Hook
Add this to .claude/settings.json in your project root:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Grep|Glob",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/approve-readonly.sh"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/approve-safe-bash.sh"
}
]
}
]
}
}
Create .claude/hooks/approve-readonly.sh:
#!/bin/bash
# Auto-approve all read-only tools (Read, Grep, Glob)
# These tools cannot modify files — safe to skip the prompt
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
permissionDecisionReason: "Read-only operation auto-approved"
}
}'
Create .claude/hooks/approve-safe-bash.sh:
#!/bin/bash
# Auto-approve safe bash commands, prompt for everything else
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
# Safe read-only commands — extend this list as needed
SAFE_PATTERNS="^(ls|cat|head|tail|grep|rg|find|wc|git status|git log|git diff|git branch|pwd|echo|which|type|file|stat)( |$)"
if echo "$COMMAND" | grep -qE "$SAFE_PATTERNS"; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
permissionDecisionReason: "Safe read-only bash command"
}
}'
else
# Fall through to normal permission prompt
exit 0
fi
Make them executable:
chmod +x .claude/hooks/approve-readonly.sh .claude/hooks/approve-safe-bash.sh
Why It Works
Claude Code fires a PreToolUse event before every tool invocation. Your hook intercepts it, inspects the tool name and input, and returns a JSON decision: "allow", "deny", or nothing (falls through to the normal prompt).
The matcher field is a regex. "Read|Grep|Glob" catches all three read-only tools. The Bash hook checks the actual command against a safe-command regex — ls, cat, git status, etc. The ( |$) at the end matches commands both with arguments (ls -la) and without (ls). Anything not on the list still triggers the normal approval prompt.
Bonus: Share It With Your Team
Commit .claude/settings.json and the hooks directory to your repo. Every developer who clones the project gets the same auto-approve behavior. No per-machine setup.
If you want these hooks globally (all projects), put the config in ~/.claude/settings.json instead. For personal tweaks that won't affect teammates, use .claude/settings.local.json — same format, automatically gitignored.
Gotcha: The regex uses ( |$) to avoid partial matches — so rm in rmarkdown won't accidentally match a rm safe-pattern. Keep destructive commands (rm, mv, chmod, docker) off the list.
Top comments (0)