---
title: "How Claude Code Hooks Work - Events, Matchers, and the PreToolUse Enforcement Point"
description: "Claude Code hooks run your code at fixed points in the agent's lifecycle. This guide explains the events, matchers, and decision control, and why PreToolUse is the enforcement point that matters."
url: "https://anomity.ai/blog/how-claude-code-hooks-work/"
source: html
---

On this page

- What a hook is
- The lifecycle: where hooks fire
- Anatomy of a hook configuration
- Decision control: how a hook says no
- Why PreToolUse is the enforcement point that matters
- Hooks as an attack surface
- How Anomity uses this boundary
- The bottom line
- Frequently asked questions

[← Back to blog](https://anomity.ai/blog/)

- Home

- Blog

- How Claude Code Hooks Work - Events, Matchers, and the PreToolUse Enforcement Point

![Anomity robot illustrating How Claude Code Hooks Work - Events, Matchers, and the PreToolUse Enforcement Point]

Guide

# How Claude Code Hooks Work - Events, Matchers, and the PreToolUse Enforcement Point

Anomity Research
Anomity Research
·
Jul 18, 2026
·
6 min read

Share: Copied

TL;DR

- Hooks are user-defined handlers - shell commands, HTTP endpoints, MCP tools, or prompts - that Claude Code runs automatically at fixed points in its lifecycle, configured under the hooks key in settings.json.
- Events cover the whole session: SessionStart, UserPromptSubmit, PreToolUse and PostToolUse around every tool call, Stop when a turn ends, plus notification, compaction, and config-change events.
- PreToolUse is the enforcement point: a hook sees the exact tool call as JSON before it runs and returns allow, deny, or ask, deterministically - no prompting or model goodwill involved.
- Decision control works through exit codes (0 with JSON output, 2 to block) and structured JSON fields like permissionDecision and updatedInput.
- Hooks are also an attack surface: they execute with your full user permissions, and a hook in a repo's committed settings arrives with a git clone.
- Anomity enforces at exactly this boundary - its Endpoint Sensor evaluates each tool call at the agent hook and returns allow, deny, or log before it runs - and inventories hooks as one of the eight AI artifact types.

Most of what people write about securing coding agents amounts to asking the model nicely. Claude Code hooks are the other thing: a documented mechanism for running your own code at fixed points in the agent's lifecycle, with the power to block, rewrite, or annotate what the agent does - deterministically, at the application layer, regardless of what any prompt says. If you operate Claude Code across a team, hooks are simultaneously your best enforcement primitive and a config surface you need to govern. This guide covers both, based on the current hooks reference. That matters because models do not always take a block as final: [OpenAI's GPT-6.1 Sol system card](https://anomity.ai/blog/gpt-6-1-sol-system-card-agent-behavior/) reports agents trying a different channel in 23.5% of blocked low-stakes tasks.

## What a hook is

A hook is a handler registered for a lifecycle event. When the event fires, Claude Code invokes the handler with a JSON payload describing what is happening - session id, working directory, permission mode, and event-specific fields like the tool name and its full input. Handlers come in several types: **command** (a shell command or executable, JSON on stdin), **http** (the payload POSTed to a URL), **mcp_tool** (a tool call on a connected MCP server), **prompt** (a single-turn model evaluation returning a yes/no decision), and an experimental **agent** type that spawns a subagent to verify a condition. All matching hooks for an event run in parallel, duplicates are deduplicated, and each has a timeout (600 seconds by default for commands).

## The lifecycle: where hooks fire

The core hook lifecycle: session and prompt events wrap the agent loop, and PreToolUse/PostToolUse wrap every individual tool call inside it.

The event catalog is large - it now covers notifications, subagent start and stop, context compaction, config changes, file watches, and worktree lifecycle - but a handful of events do most of the work in practice:

Event Fires Typical use

SessionStart When a session begins or resumes Inject context (branch, open issues), set up the environment

UserPromptSubmit When a prompt is submitted, before the model sees it Validate or enrich prompts; block disallowed requests

PreToolUse Before every tool call executes Policy enforcement: allow, deny, ask, or rewrite the call

PostToolUse After a tool call succeeds Auto-lint and format, validate results, feed context back

Stop When Claude finishes responding Verify the work is actually done; block premature stops

Notification / SessionEnd On notifications and session close Alerting, cleanup, logging

## Anatomy of a hook configuration

Hooks live under the hooks key in settings. Each event maps to a list of matcher groups; each group carries the handlers to run when the matcher fits. A matcher is a plain string for exact tool names (Bash), a pipe list (Edit|Write), or a regex (mcp__memory__.*) - which is also how you scope hooks to MCP tools, since those are named mcp__server__tool. Tool events additionally accept an if condition in permission-rule syntax, like Bash(git *), evaluated against the actual command.

```
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": [],
"timeout": 30
}
]
}
]
}
}
```

Where a hook is defined determines who controls it, and the scopes stack from personal preference to fleet policy:

Location Scope Governance note

~/.claude/settings.json One user, all projects Personal; invisible to the team

.claude/settings.json (committed) Everyone who clones the project Shared - and arrives with a git clone, trusted or not

.claude/settings.local.json One user, one project Gitignored; escapes review by design

Managed policy settings Organization-wide Admin-controlled; can set allowManagedHooksOnly to block all other hooks

Plugin hooks.json / skill frontmatter While the component is active Third-party code registering handlers on your machine

## Decision control: how a hook says no

Command hooks communicate through exit codes. Exit 0 means success, and stdout is parsed for JSON directives. Exit 2 is a blocking error: the action is stopped and stderr goes to Claude as the explanation. Any other exit code is a non-blocking error. The richer channel is JSON on exit 0. For PreToolUse, the hook returns a permissionDecision - **allow** approves the call, **deny** blocks it with a reason the model sees, **ask** escalates to a user prompt, **defer** falls through to the normal permission flow. The same JSON can rewrite the tool's arguments with updatedInput (for example, swapping a risky command for a safe one) or attach additionalContext for the model. Universal fields like continue:false stop the agent entirely, and systemMessage surfaces a warning to the user.

PreToolUse decision control: the hook sees the exact tool call as JSON and returns allow, ask, or deny before anything executes.

A minimal deny hook makes the mechanics concrete. This script reads the proposed Bash command from stdin and refuses destructive deletes, with a reason that goes back to the model:

```
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive rm -rf blocked by policy"
}
}'
else
exit 0
fi
```

## Why PreToolUse is the enforcement point that matters

Everything an agent does to the world - run a command, edit a file, call an MCP tool, make a request - is a tool call, and every tool call passes through PreToolUse before it executes. That makes this one event categorically different from the rest of the catalog. Instructions in CLAUDE.md can be ignored or [poisoned](https://anomity.ai/threats/malicious-ai-rules/); permission rules are static patterns; but a PreToolUse hook is code that inspects the exact action and rules on it, every time, even when the agent has been steered by [prompt injection](https://anomity.ai/threats/prompt-injection/). It is where an injected read-secrets-and-exfiltrate chain actually breaks: the injection can put intent into the model, but the tool call still has to get past the hook. This layering - rules for cheap static cases, hooks for real policy - is the architecture we recommend in [the Claude Code permissions and hooks hardening guide](https://anomity.ai/blog/claude-code-permissions-hooks-hardening-guide/), and it is why bypassing the permission layer entirely is such a consequential choice, as covered in [what dangerously-skip-permissions actually does](https://anomity.ai/blog/claude-code-dangerously-skip-permissions-explained/).

## Hooks as an attack surface

The same properties that make hooks a great control make them a target. A hook executes with your full user permissions, on every matching event, with no per-run prompt. Three arrival paths deserve attention. First, **repo-borne hooks**: a committed .claude/settings.json means cloning a repository can install handlers that run on your machine - the same class of problem as [project files that execute on open](https://anomity.ai/threats/code-execution/). Second, **component-bundled hooks**: plugins and skills can register hooks in their manifests and frontmatter, so a [malicious skill](https://anomity.ai/blog/what-are-agent-skills-security-primer/) is also a hook-delivery vehicle. Third, **persistence**: an agent that has been injected once can write a hook that reloads on every future session, outliving the compromised conversation. The operational answer is the same as for every artifact in the agentic stack: inventory which hooks exist on which endpoints, know where each came from, treat hook changes as events worth reviewing, and use managed settings with allowManagedHooksOnly where fleet policy demands it. The audit side of that story is covered in [auditing Claude Code across a fleet](https://anomity.ai/blog/auditing-claude-code-across-a-fleet/).

## How Anomity uses this boundary

Anomity's runtime governance is built on exactly the mechanism this guide describes. On agents that expose a hook - Claude Code's PreToolUse is the canonical example - the lightweight, unprivileged Endpoint Sensor evaluates each tool call against organizational policy and returns allow, deny, or log before it runs, fleet-wide and centrally managed instead of per-laptop shell scripts. Hooks themselves are one of the eight AI artifact types the sensor discovers and inventories, alongside agents, MCP servers, extensions, plugins, skills, secrets, and CLIs, with source, owner, and version for each - so a new or modified hook on any endpoint surfaces as a change event rather than a silent addition. The sensor sends metadata only, never source or prompts, violations route to SIEM, Slack, email, and Jira, and every decision lands in a queryable 90-day audit trail. Rolling this out across a team is covered in [deploying Claude Code across a fleet](https://anomity.ai/blog/deploying-claude-code-across-a-fleet-guide/).

> You can't govern what you can't see. The Anomity principle

## The bottom line

Hooks are the most underused security feature in Claude Code. They turn policy from a request into a guarantee: every prompt, every tool call, and every turn can pass through your code, and PreToolUse in particular gives you a deterministic allow, deny, or ask on every action the agent takes. Use them - and govern them, because a hook is executable config that arrives through repos, plugins, and skills, and runs with everything you have. Start with a deny hook for the commands you never want an agent to run, layer permission rules underneath, and put hook inventory on the same footing as the rest of your AI artifact inventory. To see every hook, agent, and tool call across your fleet, [book a 30-minute demo](https://anomity.ai/#early-access).

Share: Copied

## Frequently asked questions

What are Claude Code hooks in plain terms?
Hooks are pieces of your own logic that Claude Code runs automatically at fixed points in its lifecycle: when a session starts, when you submit a prompt, before and after every tool call, and when the agent finishes a turn. A hook can be a shell command, an HTTP endpoint, an MCP tool call, or a prompt evaluated by a model. Because hooks execute deterministically at the application layer, they turn suggestions like "always run the linter" into code that always runs.

What is the PreToolUse hook and why does it matter for security?
PreToolUse fires before any tool call executes - a shell command, a file edit, an MCP tool. The hook receives the full tool call as JSON on stdin and can return a permissionDecision of allow, deny, or ask (or defer to the normal permission flow), with a reason the model sees. It is the one point where every action the agent wants to take passes through your code before it happens, which makes it the natural enforcement point for policy - and the hook Anomity uses for runtime governance.

How do hook matchers work?
Each hook entry has an optional matcher evaluated against the event's subject - the tool name for tool events, the notification type for Notification, and so on. A plain string like Bash matches exactly, Edit|Write matches either, and anything with other characters is treated as an unanchored regex, so mcp__memory__.* matches every tool from one MCP server. An omitted matcher or "*" matches everything. Tool-event hooks can add an if condition using permission-rule syntax, like Bash(git *), to fire only on matching commands.

How does a hook block an action?
Two ways. A command hook can exit with code 2, which blocks the action and feeds its stderr to Claude as the error message. Or it can exit 0 and print JSON: for PreToolUse, hookSpecificOutput.permissionDecision set to deny blocks the call with a reason, ask escalates to the user, and allow approves it. Exit codes other than 0 and 2 are non-blocking errors. JSON output can also rewrite the tool's arguments via updatedInput or inject guidance via additionalContext.

Where are hooks configured and who controls them?
Hooks live under the hooks key in settings files: ~/.claude/settings.json for the user, .claude/settings.json committed in a project, .claude/settings.local.json ungoverned by git, managed policy settings controlled by an organization, plus hooks bundled by plugins and declared in skill or agent frontmatter. Managed settings win: an organization can ship fleet-wide hooks and use allowManagedHooksOnly to stop user, project, and plugin hooks from running at all.

Are Claude Code hooks a security risk themselves?
Yes, treated carelessly. A hook is arbitrary code that runs with your full user permissions every time its event fires, without a per-run prompt. A malicious hook in a repository's committed .claude/settings.json arrives with a git clone, and a hook added by an injected agent is durable persistence. Hooks deserve the same review as any executable config: know which hooks exist on which machines, where each came from, and when they change. Hooks are one of the eight AI artifact types Anomity inventories for exactly this reason.

How is a hook different from a permission rule or a CLAUDE.md instruction?
A CLAUDE.md instruction is a request to the model - it usually works, and reliably enough for style, but nothing enforces it. Permission rules (allow and deny lists) are enforced but static string patterns. A hook is enforced and programmable: it can inspect the exact tool input, consult external systems, rewrite arguments, and return a decision with a reason. The layers stack well - rules for the easy cases, hooks for policy that needs logic. See [how Claude Code permissions work](https://anomity.ai/blog/how-claude-code-permissions-work/).

How does Anomity relate to Claude Code hooks?
Anomity's Endpoint Sensor uses the agent's hook - Claude Code's PreToolUse - as its enforcement point: each tool call is evaluated against policy and receives allow, deny, or log before it runs, and every decision lands in a queryable 90-day audit trail with violations routed to SIEM, Slack, email, and Jira. The sensor also discovers hooks themselves across the fleet, as one of eight AI artifact types, and surfaces hook and config changes as change events.

## Related

[Advisory High ### OpenCode Server Cross-Site Upgrade Installs Attacker Packages - GHSA-632h-h47v-g4x4 A web page could make OpenCode's local server install an attacker's npm package via a cross-site form. No DNS rebinding needed. Fixed in 1.18.22. Anomity Research · Oct 2, 2026 · GHSA-632h-h47v-g4x4 (CVSS 7.5, no CVE)](https://anomity.ai/blog/opencode-cross-site-upgrade-rce-ghsa-632h-h47v-g4x4/)

[Advisory Critical ### GitSpawn - Malicious .git Configs Make Coding Agents Run Attacker Code Manifold's GitSpawn (Sep 1 2026): malicious .git/config core.fsmonitor runs on git status/diff during agent context gather - outside sandbox, no approval. Patched in goose, Codex, Cursor, Claude Code; several agents still open. Anomity Research · Sep 14, 2026 · GitSpawn (Manifold, 2026-09-01); CVE-2026-72718 (goose), CVE-2026-19592 (Codex CLI), CVE-2026-71963 (Hermes)](https://anomity.ai/blog/gitspawn-malicious-git-config-coding-agent-rce/)

[Guide ### When a Coding Agent Drops Your Database: Why It Happens and the Guardrails That Stop It Agents drop databases because they hold write credentials and migration tooling is destructive by design. The fix is credentials and deny-lists, not better prompts. Anomity Research · Aug 5, 2026 · 6 min](https://anomity.ai/blog/agents-destructive-database-operations-guardrails/)

## Structured data

```json
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "How Claude Code Hooks Work - Events, Matchers, and the PreToolUse Enforcement Point",
  "description": "Claude Code hooks run your code at fixed points in the agent's lifecycle. This guide explains the events, matchers, and decision control, and why PreToolUse is the enforcement point that matters.",
  "datePublished": "2026-07-18",
  "dateModified": "2026-07-18",
  "author": {
    "@type": "Organization",
    "name": "Anomity",
    "url": "https://anomity.ai/",
    "sameAs": [
      "https://www.linkedin.com/company/anomity",
      "https://github.com/Anomity-ai",
      "https://www.wikidata.org/wiki/Q140763940"
    ]
  },
  "publisher": {
    "@type": "Organization",
    "name": "Anomity",
    "url": "https://anomity.ai/",
    "sameAs": [
      "https://www.linkedin.com/company/anomity",
      "https://github.com/Anomity-ai",
      "https://www.wikidata.org/wiki/Q140763940"
    ],
    "logo": {
      "@type": "ImageObject",
      "url": "https://anomity.ai/icon-512.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://anomity.ai/blog/how-claude-code-hooks-work/"
  },
  "image": {
    "@type": "ImageObject",
    "url": "https://anomity.ai/assets/blog/covers/coding-agents.jpg",
    "width": 1200,
    "height": 630
  },
  "articleSection": "AI Agent & CLI Security",
  "url": "https://anomity.ai/blog/how-claude-code-hooks-work/",
  "keywords": "Claude Code hooks, Claude Code hooks, PreToolUse, Claude Code security, agent governance, tool-call enforcement, AI agent audit trail"
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "What are Claude Code hooks in plain terms?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Hooks are pieces of your own logic that Claude Code runs automatically at fixed points in its lifecycle: when a session starts, when you submit a prompt, before and after every tool call, and when the agent finishes a turn. A hook can be a shell command, an HTTP endpoint, an MCP tool call, or a prompt evaluated by a model. Because hooks execute deterministically at the application layer, they turn suggestions like \"always run the linter\" into code that always runs."
      }
    },
    {
      "@type": "Question",
      "name": "What is the PreToolUse hook and why does it matter for security?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "PreToolUse fires before any tool call executes - a shell command, a file edit, an MCP tool. The hook receives the full tool call as JSON on stdin and can return a permissionDecision of allow, deny, or ask (or defer to the normal permission flow), with a reason the model sees. It is the one point where every action the agent wants to take passes through your code before it happens, which makes it the natural enforcement point for policy - and the hook Anomity uses for runtime governance."
      }
    },
    {
      "@type": "Question",
      "name": "How do hook matchers work?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Each hook entry has an optional matcher evaluated against the event's subject - the tool name for tool events, the notification type for Notification, and so on. A plain string like Bash matches exactly, Edit|Write matches either, and anything with other characters is treated as an unanchored regex, so mcp__memory__.* matches every tool from one MCP server. An omitted matcher or \"*\" matches everything. Tool-event hooks can add an if condition using permission-rule syntax, like Bash(git *), to fire only on matching commands."
      }
    },
    {
      "@type": "Question",
      "name": "How does a hook block an action?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Two ways. A command hook can exit with code 2, which blocks the action and feeds its stderr to Claude as the error message. Or it can exit 0 and print JSON: for PreToolUse, hookSpecificOutput.permissionDecision set to deny blocks the call with a reason, ask escalates to the user, and allow approves it. Exit codes other than 0 and 2 are non-blocking errors. JSON output can also rewrite the tool's arguments via updatedInput or inject guidance via additionalContext."
      }
    },
    {
      "@type": "Question",
      "name": "Where are hooks configured and who controls them?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Hooks live under the hooks key in settings files: ~/.claude/settings.json for the user, .claude/settings.json committed in a project, .claude/settings.local.json ungoverned by git, managed policy settings controlled by an organization, plus hooks bundled by plugins and declared in skill or agent frontmatter. Managed settings win: an organization can ship fleet-wide hooks and use allowManagedHooksOnly to stop user, project, and plugin hooks from running at all."
      }
    },
    {
      "@type": "Question",
      "name": "Are Claude Code hooks a security risk themselves?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes, treated carelessly. A hook is arbitrary code that runs with your full user permissions every time its event fires, without a per-run prompt. A malicious hook in a repository's committed .claude/settings.json arrives with a git clone, and a hook added by an injected agent is durable persistence. Hooks deserve the same review as any executable config: know which hooks exist on which machines, where each came from, and when they change. Hooks are one of the eight AI artifact types Anomity inventories for exactly this reason."
      }
    },
    {
      "@type": "Question",
      "name": "How is a hook different from a permission rule or a CLAUDE.md instruction?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "A CLAUDE.md instruction is a request to the model - it usually works, and reliably enough for style, but nothing enforces it. Permission rules (allow and deny lists) are enforced but static string patterns. A hook is enforced and programmable: it can inspect the exact tool input, consult external systems, rewrite arguments, and return a decision with a reason. The layers stack well - rules for the easy cases, hooks for policy that needs logic. See [how Claude Code permissions work](/blog/how-claude-code-permissions-work/)."
      }
    },
    {
      "@type": "Question",
      "name": "How does Anomity relate to Claude Code hooks?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Anomity's Endpoint Sensor uses the agent's hook - Claude Code's PreToolUse - as its enforcement point: each tool call is evaluated against policy and receives allow, deny, or log before it runs, and every decision lands in a queryable 90-day audit trail with violations routed to SIEM, Slack, email, and Jira. The sensor also discovers hooks themselves across the fleet, as one of eight AI artifact types, and surfaces hook and config changes as change events."
      }
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://anomity.ai/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Blog",
      "item": "https://anomity.ai/blog/"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "How Claude Code Hooks Work - Events, Matchers, and the PreToolUse Enforcement Point",
      "item": "https://anomity.ai/blog/how-claude-code-hooks-work/"
    }
  ]
}
```
