Hook if Field — Conditional Execution

New in Claude Code v2.1.85. The if field lets you specify when a hook should run using permission rule syntax. If the command doesn't match, the hook process is never spawned.

The Problem

Without if, every Bash command triggers every hook with "matcher": "Bash". Running ls spawns your git-only hook. Running echo spawns your npm-only hook. With 10 hooks, a simple ls spawns 10 processes.

Before (v2.1.84 and earlier):
{
  "matcher": "Bash",
  "hooks": [{
    "type": "command",
    "command": "~/.claude/hooks/branch-guard.sh"
  }]
}

branch-guard.sh runs for every Bash command. It checks internally and exits early for non-git commands. But the process is already spawned.

The Solution

After (v2.1.85+):
{
  "matcher": "Bash",
  "hooks": [{
    "type": "command",
    "if": "Bash(git push *)",
    "command": "~/.claude/hooks/branch-guard.sh"
  }]
}

branch-guard.sh only runs when the command starts with git push. Zero process spawning for ls, echo, npm test.

Syntax

The if field uses the same syntax as permissions.allow / permissions.deny:

Bash(git *)         — git commands
Bash(npm install *)  — npm install
Bash(rm *)          — rm commands
Edit(*.py)          — Python file edits
Write(*.json)       — JSON file writes

Important Details

Real Impact

CommandBefore (10 hooks)After (with if)
ls10 processes0
git push10 processes1-2
npm test10 processes1

Get 406 hook examples with if-field documentation:

npx cc-safe-setup

9,677 tests 605 examples v2.1.85 ready

Japanese guide (Qiita) · Official docs · GitHub

cc-safe-setup · 628 hooks · 9,677 tests · GitHub

Learn more: Production Guide · All Tools