How Can Claude Code Hooks Automate Permission Management and Cut 90% of Clicking?
I was editing a React component in Claude Code when I hit my twentieth permission prompt in ten minutes. “Approve this edit? Approve this read? Approve this git command?” My flow was shattered. Each approval click felt like a tiny tax on my attention.
I tried to find a pattern in the interruptions. Most were predictable: edits in my project directory, npm commands, git operations. All safe. All repetitive. All requiring manual approval.
Then I discovered hooks.
The Problem: Permission Prompt Fatigue
Every file edit, terminal command, and tool operation in Claude Code triggers a permission prompt. During active development sessions, I was seeing dozens or hundreds of approval clicks. This friction:
- Breaks flow state
- Creates cognitive overhead
- Slows down trusted operations
- Makes Claude Code feel less like a pair programmer
The prompts exist for good reason: security. But most operations in a trusted codebase are safe. I needed a way to distinguish between “always safe” operations and “needs review” operations.
The Solution: PreToolUse Hooks
Claude Code hooks let you intercept tool calls before execution (PreToolUse) and after execution (PostToolUse). The key unlock for automated permissions is pattern matching on incoming tool calls.
I configured hooks to:
- Match tool operations against trusted patterns
- Auto-approve when safe conditions are met
- Still require approval for anything outside the patterns
The result? I cut out 90% of the approve/deny clicking. Let me show you how.
Setting Up Automated Permission Hooks
First, I looked at my most common operations:
- Read operations: Always safe (I can’t think of a reason to block reads)
- Edits in my project directory: Safe during development
- npm commands: Safe (tests, installs, builds)
- git commands: Safe for local operations
I created patterns for each category and added them to my hooks configuration.
Method 1: Using allowedTools for Simple Auto-Approvals
The simplest approach uses the allowedTools array in ~/.claude/settings.json:
{ "allowedTools": [ "Read", "Edit:./src/**", "Edit:./components/**", "Bash:npm run *", "Bash:npm test*", "Bash:git status*", "Bash:git diff*", "Bash:git log*", "Glob", "Grep" ]}This tells Claude Code: “These operations are always approved.” The pattern syntax:
ToolName- Allow all uses of this toolToolName:pattern- Allow only when matching the pattern*- Wildcard matching
Method 2: Advanced PreToolUse Hooks
For more control, I use PreToolUse hooks with matchers:
{ "hooks": { "PreToolUse": [ { "matcher": { "toolName": "Edit", "filePath": "/Users/cowrie/projects/myapp/**" }, "hooks": [ { "type": "auto-approve", "reason": "Editing files in myapp project is trusted" } ] }, { "matcher": { "toolName": "Bash", "command": "npm test*" }, "hooks": [ { "type": "auto-approve", "reason": "Running tests is always safe" } ] }, { "matcher": { "toolName": "Read" }, "hooks": [ { "type": "auto-approve", "reason": "Read operations are safe" } ] } ] }}The matcher object supports:
toolName: Which tool to matchfilePath: Path pattern for file operationscommand: Command pattern for terminal operations
Understanding the Hook Flow
Here’s what happens when Claude Code wants to execute a tool:
┌─────────────────────┐│ Tool Call Requested │└──────────┬──────────┘ │ ▼┌─────────────────────┐│ PreToolUse Hook ││ Check Matchers │└──────────┬──────────┘ │ ┌──────┴──────┐ │ │ ▼ ▼┌─────────┐ ┌──────────┐│ Auto- │ │ Show ││ Approve │ │ Permission│└────┬────┘ │ Prompt │ │ └────┬─────┘ │ │ └─────┬──────┘ │ ▼┌─────────────────────┐│ Tool Executes │└─────────────────────┘ │ ▼┌─────────────────────┐│ PostToolUse Hook ││ (logging, etc.) │└─────────────────────┘The hook inspects the tool call, evaluates conditions, and returns an auto-approval when matched. If no pattern matches, the normal permission prompt appears.
My Trial-and-Error Process
Attempt 1: Too Permissive
I started with:
{ "allowedTools": ["*"]}Bad idea. This auto-approved everything, including destructive operations. I quickly reverted and thought more carefully about what’s truly safe.
Attempt 2: Directory-Specific Rules
I refined my approach to project-specific rules:
{ "allowedTools": [ "Read", "Edit:./src/**", "Edit:./tests/**", "Bash:npm *", "Bash:git status", "Bash:git diff", "Bash:git log" ]}This worked well but was too conservative. I still got prompts for git commands like git branch and git add.
Attempt 3: Pattern-Based Approach
I analyzed a week of my Claude Code sessions and identified patterns:
Tool | Pattern | Safety---------|---------------------|--------Read | * | SafeEdit | ./src/** | SafeEdit | ./tests/** | SafeEdit | ./docs/** | SafeBash | npm run * | SafeBash | npm test* | SafeBash | git status | SafeBash | git diff* | SafeBash | git log* | SafeBash | git branch* | SafeBash | git add * | Needs reviewBash | git commit * | Needs reviewBash | git push * | Needs reviewBash | rm * | Needs reviewBash | *destroy* | Never autoThis analysis led to my final configuration:
{ "allowedTools": [ "Read", "Glob", "Grep", "Edit:./src/**", "Edit:./tests/**", "Edit:./docs/**", "Edit:./components/**", "Edit:./lib/**", "Edit:./utils/**", "Bash:npm run *", "Bash:npm test*", "Bash:npm install*", "Bash:git status*", "Bash:git diff*", "Bash:git log*", "Bash:git branch*", "Bash:git checkout*", "Bash:pnpm *", "Bash:yarn *" ]}The key principle: directory-specific rules over global wildcards.
Combining with Other Safety Measures
Hooks work best as part of a broader automation strategy. From community discussions, I learned about combining hooks with:
- Isolated worktrees per task: Each task gets its own worktree, so mistakes are contained
- Strict MCP/tool allowlists: Only enable the MCP servers and tools you need
- Automated checks before merge: Force tests + diff checks
Example workflow:
┌─────────────────┐│ Create Worktree │└────────┬────────┘ │ ▼┌─────────────────┐│ Hooks Auto- ││ Approve Edits │└────────┬────────┘ │ ▼┌─────────────────┐│ Tests Run Auto ││ (via PostTool) │└────────┬────────┘ │ ▼┌─────────────────┐│ Diff Check ││ Before Commit │└────────┬────────┘ │ ▼┌─────────────────┐│ Manual Review ││ for git push │└─────────────────┘Common Mistakes to Avoid
Mistake 1: Auto-Approving All Operations
// WRONG: This is dangerous{ "allowedTools": ["*"]}This defeats the purpose of permission checks entirely.
Mistake 2: Using Global Patterns Instead of Directory-Specific
// WRONG: Too broad{ "allowedTools": ["Edit:*"]}
// BETTER: Directory-specific{ "allowedTools": ["Edit:./src/**"]}Mistake 3: Auto-Approving Destructive Commands
// WRONG: Never auto-approve deletions{ "allowedTools": ["Bash:rm *"]}
// WRONG: Never auto-approve pushes{ "allowedTools": ["Bash:git push*"]}Mistake 4: Not Reviewing Hook Configurations
Hooks apply across all Claude Code sessions. Set a reminder to review your allowedTools monthly:
# Quick check of your current configurationcat ~/.claude/settings.json | jq '.allowedTools'Mistake 5: Mixing Auto-Approve Rules with Critical Operations
Keep separate patterns for:
- Development: Auto-approve with hooks
- Deployment: Always require manual approval
- Production access: Never include in allowedTools
Why This Matters
After implementing hooks, my workflow changed dramatically:
| Metric | Before Hooks | After Hooks |
|---|---|---|
| Approval clicks per session | 50-100 | 5-10 |
| Context switches | Frequent | Rare |
| Flow state interruptions | Every 2-3 minutes | Every 30+ minutes |
| Trust level | Manual verification | Pattern-verified |
The key insight: trust patterns, not individual operations. When I trust that edits in ./src/** are always safe, I don’t need to verify each one. The pattern becomes the verification.
Trade-offs and Considerations
Hooks aren’t perfect:
- Overly permissive patterns reduce security: Start conservative, expand carefully
- Hooks apply globally: A pattern that works for one project might not work for another
- No undo for auto-approved operations: Make sure your git workflow has safety nets
- Debugging can be harder: If something goes wrong, check if a hook auto-approved it
The security vs. convenience trade-off is real. I prefer:
// Security-first approach{ "allowedTools": [ "Read", "Edit:./src/**", "Bash:npm test*" ]}Over:
// Convenience-first approach (riskier){ "allowedTools": [ "Read", "Edit:*", "Bash:npm *", "Bash:git *" ]}Implementation Checklist
Before deploying hooks:
- Audit your common operations for a week
- Categorize operations by safety level
- Start with read-only operations
- Add directory-specific edit rules
- Test with a non-critical project first
- Review hook configurations monthly
- Keep destructive operations manual
- Document your allowedTools in version control
Related Knowledge
Claude Code hooks are part of a broader ecosystem:
- PreToolUse hooks: Intercept before execution (validation, auto-approval)
- PostToolUse hooks: React after execution (logging, auto-formatting)
- Stop hooks: Execute when session ends (cleanup, verification)
The pattern-matching approach is similar to:
- Git’s
.gitignorepatterns - ESLint’s override patterns
- AWS IAM policy conditions
Reference Links
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments