How to Configure and Use Claude Code Hooks: Matcher Syntax, Hook Types, and Runtime Controls
Claude Code hooks are extensibility scripts declared in hooks/hooks.json that execute at specific lifecycle phases, filtered by matcher syntax like Bash|Write or *, and controlled through profile flags parsed by scripts/hooks/run-with-flags.js.
Claude Code hooks enable developers to intercept tool executions and session events through a declarative JSON configuration system. This guide covers the architecture implemented in the everything-claude-code repository, detailing how to configure hook phases, leverage matcher syntax for precise targeting, and apply runtime controls for secure, conditional execution.
Hook Lifecycle Phases
The hooks/hooks.json registry defines six distinct phases where Claude Code hooks can intercept execution. The engine iterates over hook arrays in the order declared under each top-level key:
- PreToolUse: Executes immediately before any tool (Bash, Write, Edit, MultiEdit) runs. Use this phase to validate arguments or enforce security policies.
- PreCompact: Fires before context-compaction operations. Ideal for persisting state or dumping diagnostics.
- SessionStart: Triggers at the beginning of a new Claude Code session. Commonly used to detect package managers or restore previous context.
- PostToolUse: Runs after a tool finishes execution. Supports asynchronous operations for non-blocking quality gates or governance capture.
- Stop: Executes after every assistant response, before the next user input. Use for scanning prohibited patterns or storing session metadata.
- SessionEnd: Runs when the session terminates (e.g., upon
/stopcommand). Handles cleanup, final markers, or analytics flushing.
Each phase contains an array of hook objects that the runtime engine processes sequentially.
Matcher Syntax for Targeting Tools
The matcher field determines which tool invocations trigger a hook. The syntax supports three patterns:
- Exact tool names:
Bash,Write,Edit, orMultiEdit - Pipe-separated alternatives:
Bash|Write|Editmatches any of the listed tools - Wildcard:
*matches every tool invocation
The engine performs simple string comparison against the current tool name. No regular expressions are required.
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "node scripts/hooks/suggest-compact.js"
}
],
"description": "Suggest manual compaction at logical intervals"
}
This configuration triggers only when the Edit or Write tools are invoked.
Declaring Hooks in hooks.json
A valid hook object requires three properties:
matcher: String defining which tools activate the hook (see Matcher Syntax above)hooks: Array of action objects, each containingtype(typically"command") and the shellcommandto executedescription: Human-readable explanation displayed in the UI
Optional fields control execution behavior:
async(boolean): Whentrue, the hook runs in the background without blocking the main flow. Essential for expensive analysis that must not delay user responses.timeout(seconds): Maximum execution duration before the engine forcibly terminates the hook process.
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node scripts/hooks/telemetry.js",
"async": true,
"timeout": 5
}
],
"description": "Async telemetry for every tool use"
}
Runtime Controls and Security
The scripts/hooks/run-with-flags.js dispatcher implements granular controls for Claude Code hooks through profile flags and security sandboxing.
Profile-Based Flagging
Hooks can be conditionally enabled via the isHookEnabled(hookId, { profiles }) function (lines 49-52 in run-with-flags.js). Pass a CSV list of profiles as the third argument to the script:
node scripts/hooks/run-with-flags.js "hook-id" "script.js" "standard,strict"
Active profiles are determined by environment variables:
ECC_ENABLE_INSAITS=1: Enables the InsAIts security monitor (PreToolUse hook, lines 78-86)ECC_GOVERNANCE_CAPTURE=1: Activates governance capture hooks (PreToolUse and PostToolUse, lines 86-98 and 190-198)ECC_HOOK_PROFILE=standard,strict: Defines which comma-separated profiles are active; defaults tostandard
If a hook's required profiles do not intersect with active profiles, the engine skips execution and passes stdin through unchanged.
Execution Modes and Timeouts
- Synchronous (default): Claude Code waits for hook completion before proceeding. Required for actions affecting the next step, such as argument validation.
- Asynchronous (
"async": true): The hook spawns in parallel; the assistant returns results immediately. Use for build notifications or heavy analysis. - Timeout enforcement: The
timeoutfield (seconds) caps execution time. The engine kills processes exceeding this limit and logs warnings (see spawning logic at lines 101-108).
Security Safeguards
The runtime implements multiple protective measures:
- Path-traversal protection:
run-with-flags.jsverifies that resolved script paths remain within the plugin root directory (lines 58-63). - Require-export optimization: Scripts exposing
module.exports.runare loaded directly viarequire()instead of child process spawning, reducing attack surface (lines 71-99). - Environment sanitization: Child processes inherit
process.env, but only explicit flags and the plugin root path influence hook enablement logic.
Practical Configuration Examples
Blocking Dangerous Git Commands
Use a PreToolUse hook with the Bash matcher to prevent --no-verify flags that bypass git hooks:
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "npx block-no-verify@1.1.2"
}
],
"description": "Block git hook-bypass flag to protect pre-commit, commit-msg, and pre-push hooks"
}
Auto-Starting Development Servers
Launch long-running dev servers
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →