Skip to content

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:

  1. Match tool operations against trusted patterns
  2. Auto-approve when safe conditions are met
  3. 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:

~/.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 tool
  • ToolName:pattern - Allow only when matching the pattern
  • * - Wildcard matching

Method 2: Advanced PreToolUse Hooks

For more control, I use PreToolUse hooks with matchers:

~/.claude/settings.json
{
"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 match
  • filePath: Path pattern for file operations
  • command: Command pattern for terminal operations

Understanding the Hook Flow

Here’s what happens when Claude Code wants to execute a tool:

Hook Execution Flow
┌─────────────────────┐
│ 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:

~/.claude/settings.json
{
"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:

My Common Operations
Tool | Pattern | Safety
---------|---------------------|--------
Read | * | Safe
Edit | ./src/** | Safe
Edit | ./tests/** | Safe
Edit | ./docs/** | Safe
Bash | npm run * | Safe
Bash | npm test* | Safe
Bash | git status | Safe
Bash | git diff* | Safe
Bash | git log* | Safe
Bash | git branch* | Safe
Bash | git add * | Needs review
Bash | git commit * | Needs review
Bash | git push * | Needs review
Bash | rm * | Needs review
Bash | *destroy* | Never auto

This analysis led to my final configuration:

~/.claude/settings.json
{
"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:

  1. Isolated worktrees per task: Each task gets its own worktree, so mistakes are contained
  2. Strict MCP/tool allowlists: Only enable the MCP servers and tools you need
  3. Automated checks before merge: Force tests + diff checks

Example workflow:

Safe Development 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:

Terminal window
# Quick check of your current configuration
cat ~/.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:

MetricBefore HooksAfter Hooks
Approval clicks per session50-1005-10
Context switchesFrequentRare
Flow state interruptionsEvery 2-3 minutesEvery 30+ minutes
Trust levelManual verificationPattern-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:

  1. Overly permissive patterns reduce security: Start conservative, expand carefully
  2. Hooks apply globally: A pattern that works for one project might not work for another
  3. No undo for auto-approved operations: Make sure your git workflow has safety nets
  4. 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

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 .gitignore patterns
  • ESLint’s override patterns
  • AWS IAM policy conditions

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