Claude Code Hooks: The Overlooked Execution Gate Explained

This article dissects Claude Code Hooks, showing how they differ from CLAUDE.md suggestions, detailing their event system, configuration layers, merging rules, real‑world examples, and a ready‑to‑use hook that protects production files from accidental modification.

Code Mala Tang
Code Mala Tang
Code Mala Tang
Claude Code Hooks: The Overlooked Execution Gate Explained
CLAUDE.md tells Claude what to do, but Hooks decide whether it will actually do it. Most people ignore this gap. You can write "do not modify prod.env" in CLAUDE.md, but whether Claude obeys depends on its momentary judgment.

CLAUDE.md is a behavioral specification for Claude, while Hooks are enforceable constraints that cannot be bypassed.

You can write a clear rule like "do not modify prod.env" in CLAUDE.md, but the rule’s effectiveness depends on the model’s judgment at that moment—an inherently unstable process.

Hooks take a completely different approach: they bypass the model’s judgment entirely. They are programmable checkpoints embedded in the Claude Code execution flow that intervene before an action occurs, rather than providing post‑hoc remediation.

What problem do Hooks solve

Claude Code is powerful: it can read files, write code, run commands, and call APIs. The more permissions you grant, the more useful it becomes.

However, the same power makes it dangerous.

What prevents it from tampering with production configuration at 2 a.m.? What guarantees that every lint rule is enforced each time? What records file reads reliably without relying on the model’s memory?

CLAUDE.md offers suggestions; Hooks provide guarantees.

The essential difference is that Hooks run real code. Their logic does not depend on whether the model understood or remembered a rule—it depends on the code you wrote in advance.

What exactly is a Hook

A Hook is not a prompt nor a way to inject extra context.

It is a programmable control mechanism embedded inside the Claude Code execution process.

When Claude Code is about to invoke a tool, write a file, or execute a command, the Hook steps in before the action and makes one of three decisions:

Allow

Block

Ask a human for confirmation

This decision comes from code you wrote beforehand, not from the model itself.

Minimal Hook configuration in settings.json looks like this:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {"type": "command", "command": "./check.sh"}
        ]
      }
    ]
  }
}

Breaking the three‑level structure down:

First layer – Event registry . The outer hooks object maps event names (e.g., PreToolUse, PostToolUse, Stop) to the points where you want to intervene.

Second layer – Matcher rules . Each event contains a list of matcher objects. For example, matcher: "Write" means the rule only triggers when Claude tries to use the Write tool; omitting a matcher matches everything.

Third layer – The actual Hook . Inside each matcher, the hooks array holds the executable logic, such as type: command (run a shell script), type: http (call a URL), or type: mcp_tool (invoke an MCP tool).

hooks                  ← system entry
└── PreToolUse       ← event: triggers before any tool call
     └── matcher    ← filter: only matches "Write" tool
          └── hook ← action: run the shell command

Hook event system

Claude Code defines 28 Hook events that cover every critical node in the execution pipeline, including session start/end, tool pre/post, permission requests, file changes, sub‑Agent tasks, and various notifications.

Important misconception: these events are siblings, not parent‑child relationships.

For example, PreToolUse and PermissionRequest often fire back‑to‑back, but they are independent entry points with separate matchers and do not trigger each other.

Events fall into two categories:

Main‑flow events (e.g., PreToolUse, PermissionRequest, PostToolUse, Stop) can block Claude; the main flow pauses until the Hook returns.

Side‑track events (e.g., Notification, ConfigChange) are observation/notification channels that do not affect the main flow.

Key distinction:

Blocking : the Hook’s result directly determines Claude’s next step; the main flow waits.

Non‑blocking : the Hook runs concurrently, but Claude proceeds without waiting—useful for logging, notifications, or state sync.

How to truly block Claude

Official documentation is vague about this.

There are two ways to block a blocking event, and they behave very differently.

Exit 2 → system error . Claude interprets this as a failure (tool unavailable, missing resource, broken environment) and may try to understand or work around the error.

Using exit 0 with a JSON payload tells Claude that a concrete business rule denies the operation. Claude accepts the decision and adjusts its behavior accordingly.

The JSON payload can also:

Provide a human‑readable rejection reason.

Modify tool input via updatedInput before Claude uses it.

Set "decision": "ask" to hand the request to a human instead of outright denying.

Common pitfall: exit 1 does nothing. In Unix it signals failure, but in Claude Code Hook semantics it is non‑blocking—Claude ignores it and continues.

Only exit 2 or exit 0 with JSON actually influence the flow.

Where Hooks live

Hooks are not limited to a personal settings.json; they can be registered in four places, each with its own scope and lifecycle.

1. Config‑level Hooks – persistent . Stored in settings.json at three levels: user‑wide ( ~/.claude/settings.json), project‑wide ( .claude/settings.json), and local overrides ( .claude/settings.local.json). They are active from the moment Claude Code starts until it exits, never cleared between tasks.

~/ .claude /settings.json   ← user‑level, binds to this machine
.claude /settings.json      ← project‑level, shared with the team
.claude /settings.local.json← local override, not committed

2. Plugin Hooks – loaded with the plugin . A plugin packages its own CLAUDE.md, Skills, and Hooks. When Claude Code loads the plugin, its Hooks merge with the main Hook set without any priority ordering.

Hard restriction: sub‑Agents inside a plugin cannot define Hooks, preventing low‑privilege agents from altering the execution control flow.

3. Skill Hooks – scoped to a Skill . Registered when a Skill is invoked and automatically cleared when the Skill finishes, leaving no global residue.

Example: a Skill prints the first 30 lines of a task plan before each Write/Edit/Bash, reminds Claude to update the plan after writing, and runs a final check when the Skill ends.

4. Sub‑Agent Hooks – scoped to a sub‑Agent . Like Skill Hooks, they are temporary and auto‑cleared, but a Stop Hook defined in a sub‑Agent’s frontmatter is transformed into SubagentStop because it ends the sub‑Agent, not the whole session.

How multiple Hooks are merged

At any moment, Hooks from different layers may be active simultaneously.

When a Write operation triggers PreToolUse, it can match user‑level, project‑level, and active Skill Hooks—all three run together.

Three rules govern the outcome:

Rule 1 – Parallel execution : All matching Hooks run concurrently, not serially or by priority. Claude waits for all to finish before deciding.

Rule 2 – Automatic deduplication : Identical Hooks (same event, matcher, and command) are de‑duplicated; only one instance runs.

Rule 3 – Strictest result wins : When Hooks return different decisions, Claude selects the most restrictive one (deny > ask > allow). A single deny overrides any allows.

User‑level Hook:    allow   (normal writes are fine)
Project‑level Hook: ask     (.env changes need confirmation)
Plugin‑level Hook:  deny    (prod.env is always prohibited)
─────────────────────────────
Final decision:     deny    (one veto is enough)

Two real Hooks worth studying

Case 1: Superpowers plugin – inject context at session start . The plugin provides a full suite of engineering‑discipline Skills (requirement clarification, planning, test‑driven development, code review). It registers a single Hook that adds the Superpowers skill commands as extra context to every new session, ensuring Claude always starts with the correct initial context.

Insight: Hooks do not always need to block or approve; sometimes simply injecting the right information at the right moment is their entire purpose.

Case 2: claude-code-warp plugin – bridge Claude’s lifecycle to the terminal . Warp is a terminal that otherwise knows nothing about Claude’s internal state. The plugin uses six Hooks to keep Warp informed:

SessionStart      → send initialization info to Warp
UserPromptSubmit  → tell Warp Claude has started working
PostToolUse       → tell Warp the blocking state is cleared
Notification      → send idle‑time notifications to Warp
PermissionRequest → send tool name + input preview to Warp
Stop              → read session, extract summary, send completion notice

The most interesting is the Stop Hook: it reads the current session, extracts the last user prompt and Claude’s reply, truncates them to a suitable length, and sends a structured summary to Warp’s notification center, turning internal session logs into a user‑readable completion notice.

Insight: Hooks can act as event bridges, synchronizing Claude Code’s execution state with external systems that would otherwise be unaware of Claude.

A practical Hook you can use today

Protect your production configuration files with a copy‑and‑paste script.

Create .claude/hooks/protect-prod.sh:

#!/usr/bin/env bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

if [[ "$file_path" == *"prod.env"* || "$file_path" == *".env.production"* ]]; then
  echo '{"decision":"deny","reason":"Production configuration file is protected, write prohibited"}'
  exit 0
fi
exit 0

Register it in .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {"type": "command", "command": ".claude/hooks/protect-prod.sh"}
        ]
      }
    ]
  }
}

Make the script executable: chmod +x .claude/hooks/protect-prod.sh From now on, any attempt to Write or Edit a production config file is intercepted before Claude reaches the file, and a clear denial reason is returned—not because Claude remembered the rule, but because the code enforces it.

Mental model for making Hooks click

Imagine Claude Code’s execution as an assembly line.

Without Hooks, Claude runs the entire line from start to finish. CLAUDE.md provides guidance, Skills organize the work, but the execution proceeds uninterrupted.

With Hooks, you can insert inspection points anywhere on the line. Each point runs real code that can examine the upcoming action, decide to allow it, modify inputs, or block it.

The system consists of three cooperating layers:

CLAUDE.md – tells Claude how to interpret the project.

Skills – turn Claude into a reliable workflow.

Hooks – guard the boundaries at critical execution nodes.

None can replace the others. Only CLAUDE.md without Hooks yields guidance without enforcement; only Hooks without CLAUDE.md enforce rules the model doesn’t understand. Together they provide reliable behavior at every level.

Warning: Hooks run real code with immediate, sometimes irreversible effects. A mistaken exit code can silently break the whole pipeline; a poorly handled Stop Hook can trap a session in a loop. Test each Hook as you would production code, handle edge cases, and log failures explicitly.

5 key takeaways

Hooks are code, not prompts; they run regardless of the model’s state. exit 1 does nothing. Use exit 2 for system errors and exit 0 with JSON for policy decisions.

Events are sibling relationships; PreToolUse and PermissionRequest fire independently.

The strictest result wins; a single deny blocks the operation, while allow requires unanimous consent.

Skill and sub‑Agent Hooks are temporary; they register on invocation and clean up automatically, leaving no global residue.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

ConfigurationsecurityHooksAI AutomationShell scriptClaude Code
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.