Skip to main content
reopt Handbook
reopt Handbook
Claude Code Complete Guide

Setup

Plan and Environment SetupMastering CLAUDE.md

Multi-session & Agents

Multi-session WorkflowSubagents

Automation & Extension

HooksMCP IntegrationSkills and Slash Commands

Workflow

Plan ModeContext ManagementIDE IntegrationHeadless and CI/CD

Practice

Vibe Coding PatternsPrompting Best Practices

Verification

Verification ReportUpdate Log
Handbook›Claude Code Complete Guide›Hooks
한국어English

Hooks

Use Claude Code hooks for predictable automation and repository guardrails.

Key takeaways

  • Hooks are user-defined shell commands that run at lifecycle events (PreToolUse, PostToolUse, SessionStart, and more), giving deterministic control instead of relying on the model to act.
  • Hooks communicate through exit codes: exit 0 proceeds, exit 2 is a blocking error whose stderr is fed back to Claude, and any other code is a non-blocking error.
  • For finer control, exit 0 and print JSON like hookSpecificOutput.permissionDecision (allow/deny/ask); never mix JSON output with exit 2.
  • Use matcher to filter by tool name and the if field (v2.1.85+) to filter by tool arguments such as "Bash(git *)" or "Edit(*.ts)".
  • Roll out gradually — log first, then warn, then block only proven-damaging commands — and set scope by file: ~/.claude/settings.json, project .claude/settings.json, or gitignored .claude/settings.local.json.

Hooks are user-defined shell commands that run at specific points in Claude Code's lifecycle. They give you deterministic control: certain actions always happen instead of relying on the model to choose to run them. Start with observability and lightweight checks before adding blocking policy.

Useful Hook Categories

Hooks attach to lifecycle events. The events you reach for most are:

  • PreToolUse for pre-action checks on risky tool calls (for example, Bash commands that match rm *).
  • PostToolUse for post-edit formatting or targeted linting, usually with an Edit|Write matcher.
  • SessionStart (with a compact matcher) to re-inject critical context after compaction.
  • CwdChanged, DirectoryAdded, and FileChanged for directory- or file-specific rules, such as reloading environment variables. DirectoryAdded fires after /add-dir or SDK register_repo_root adds a working directory mid-session (v2.1.219).
  • PermissionRequest / PermissionDenied for permission logging or auto-approval so teams can improve allowlists.

The full event list is large and still growing (it also includes UserPromptSubmit, Stop, SubagentStart / SubagentStop, Notification, PreCompact / PostCompact, SessionEnd, and more); see the official Hooks reference for the complete set and each event's input schema.

Design Rules

Hooks should be fast, deterministic, and easy to bypass only through an explicit policy decision. They should not surprise the developer with long-running work. Command, HTTP, and MCP-tool hooks time out after 10 minutes by default (UserPromptSubmit lowers that to 30 seconds); override per hook with the timeout field, expressed in seconds.

Good hook behavior:

  • Emits a clear message.
  • Names the command it ran.
  • Fails with an actionable reason on stderr so Claude can adjust.
  • Stays scoped to the changed area.

Bad hook behavior:

  • Installs packages.
  • Mutates unrelated files.
  • Runs the entire CI suite on every small edit.
  • Hides the reason a command was blocked.

How Hooks Communicate

Command hooks talk to Claude Code through stdin, stdout, stderr, and the exit code. When an event fires, Claude Code passes event data as JSON on stdin; your script reads it, does its work, and signals the result.

The exit code is the simplest control:

  • Exit 0 — no objection; the action proceeds normally. For PreToolUse this does not approve the call, the normal permission flow still applies. For UserPromptSubmit and SessionStart, anything written to stdout is added to Claude's context.
  • Exit 2 — blocking error. Stderr is fed back to Claude as feedback. The effect depends on the event: PreToolUse blocks the tool call, UserPromptSubmit rejects the prompt, Stop prevents Claude from stopping. Some events (such as SessionStart and Notification) cannot be blocked.
  • Any other exit code — non-blocking error; the action proceeds and the transcript shows a <hook name> hook error notice with the first line of stderr.

For more control, exit 0 and print a JSON object to stdout instead. A PreToolUse hook, for example, can return hookSpecificOutput.permissionDecision of "allow", "deny", or "ask". Do not mix the two styles: Claude Code ignores JSON output when you exit 2.

Reference scripts and absolute paths with ${CLAUDE_PROJECT_DIR} (project root) so they resolve no matter what the working directory is.

Example Policy

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "pnpm --filter handbook typecheck"
          }
        ]
      }
    ]
  }
}

Use this shape only when the command is fast enough for the repository. For large projects, prefer focused scripts that inspect changed files first. The matcher filters by tool name: a value with only letters, digits, _, and | is treated as an exact name or pipe-separated list, and any other character makes it a regular expression. An empty or omitted matcher fires on every occurrence of the event.

To filter by tool arguments as well as name, use the if field, which accepts permission-rule syntax such as "Bash(git *)" or "Edit(*.ts)" and only spawns the hook when the call matches. The if field requires Claude Code v2.1.85 or later (earlier versions ignore it and run on every matched call) and works only on tool events (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied).

Rollout Pattern

  1. Log the event without blocking.
  2. Add a warning for known risky patterns.
  3. Block only the commands or states that have caused real damage.
  4. Document the hook in onboarding material.
  5. Review false positives after every week of use.

Where you add a hook determines its scope: ~/.claude/settings.json applies to all your projects, .claude/settings.json is committed for a single project, and .claude/settings.local.json is the gitignored per-project file. Plugins, skills, and agent frontmatter can also contribute hooks.

Run /hooks to browse every configured hook grouped by event. The menu is read-only: to add, modify, or remove a hook, edit the settings JSON directly (or ask Claude to make the change). To turn everything off, set "disableAllHooks": true in a settings file.

Related docs

Cmd. /hooks

Codex Command Master · Review lifecycle hooks and manage trust or disabled state.

Cmd. /hooks

Claude Code Command Master · Shows configured hooks and their triggers. Editing a hook changes what runs when its event occurs.

Ch11. Schedules, State, Hooks

Enterprise Eve Agent Development · Connect Eve cron schedules, durable state, and stream-event hooks to automation, memory, and audit.

Cmd. /exit

Codex Command Master · Exit the Codex CLI session; equivalent to /quit.

Terminal CLI Commands and Flags

Claude Code Command Master · All 34 invocation forms and 79 common flags from the official Claude Code CLI reference, plus recent subcommand options

Subagents

Delegate bounded work while keeping the main Claude Code session accountable.

MCP Integration

Connect Claude Code to tools and resources through Model Context Protocol servers.

On this page

Useful Hook CategoriesDesign RulesHow Hooks CommunicateExample PolicyRollout Pattern