Guide

Claude Code hooks: events, matchers, settings and handlers

Updated

On this page

Claude Code hooks are user-defined shell commands, HTTP endpoints, MCP tool calls, model prompts or subagents that run automatically at specific points in Claude Code's lifecycle. You define them in JSON settings files using three levels of nesting: a hook event name, a matcher group to filter when the event fires, and one or more hook handlers. When a hook runs, Claude Code passes it JSON about the event and reads back an exit code or structured JSON that can allow or block the action, or add context for Claude.

Logitech MX Creative Keypad running Orbtile in Focus mode for a Claude Code permission request, with Deny on key 7 and Allow on key 9

Hook lifecycle events and when they fire

Claude Code fires the same hook events in the terminal, IDE extensions, the Desktop app and cloud sessions. Events fall into three cadences:

  • Per session: SessionStart and SessionEnd.
  • Per turn: UserPromptSubmit, Stop and StopFailure.
  • On every tool call inside the agentic loop: PreToolUse and PostToolUse, except EndConversation calls, which skip both.
EventWhen it firesWhat matcher filters
SessionStartWhen a session begins or resumesHow the session started (startup, resume, clear, compact, fork)
UserPromptSubmitWhen a prompt is submitted, before Claude processes itNo matcher support
PreToolUseBefore a tool call executes; can block itTool name (Bash, `Edit\Write, mcp__.*`)
PermissionRequestWhen a tool call needs a permission decisionTool name
PostToolUseAfter a tool call succeedsTool name
PostToolUseFailureAfter a tool call failsTool name
NotificationWhen Claude Code sends a notificationNotification type (permission_prompt, idle_prompt, agent_needs_input and others)
SubagentStartWhen a subagent is spawnedAgent type (general-purpose, Explore, Plan or custom names)
StopWhen Claude finishes respondingNo matcher support
StopFailureWhen the turn ends due to an API errorError type (rate_limit, overloaded, server_error and others)
PreCompact / PostCompactBefore context compaction, or after it completesWhat triggered compaction (manual, auto)
SessionEndWhen a session terminatesWhy the session ended (clear, resume, logout, prompt_input_exit, other)

Where to configure hooks and how settings merge

Where you place a hook determines its scope:

LocationScopeShareable
~/.claude/settings.jsonAll your projectsNo, local to your computer
.claude/settings.jsonSingle projectYes, can be committed to the repository
.claude/settings.local.jsonSingle projectNo, gitignored when Claude Code saves to it
Managed policy settingsOrganisation-wideYes, administrator-controlled
Plugin hooks/hooks.jsonWhile the plugin is enabledYes, bundled with the plugin
Skill frontmatterRest of the session once the skill is invokedYes, defined in the skill file
Subagent frontmatterWhile that subagent runsYes, defined in the subagent file
  • Additive merging: hook entries merge across settings levels rather than replacing each other, so user, project and local settings add their own hooks without removing managed ones.
  • Inspecting and reloading: type /hooks in Claude Code to open a read-only browser of your configured hooks, grouped by event. If you edit settings files directly, the file watcher normally picks up the changes.
  • Disabling hooks: set "disableAllHooks": true in a settings file. Hooks configured in managed settings still run unless disableAllHooks is also set there.

Filter hooks with matchers and the if field

How Claude Code evaluates a matcher group's matcher depends on the characters in the string:

  • Match all: omitting matcher, or setting it to "*" or "", fires the group on every occurrence of the event.
  • Exact names and lists: a string containing only letters, digits, underscores, hyphens, spaces, commas and pipes matches an exact name or a list separated by | or ,, such as "Bash" or "Edit|Write".
  • Regular expressions: any other character makes Claude Code treat the pattern as an unanchored JavaScript regular expression. For example, ^Notebook matches any tool whose name starts with Notebook.
  • MCP tools: MCP tools follow the naming pattern mcp__<server>__<tool>. To match every tool from a server, append .* to the server prefix, as in mcp__memory__.*.

On tool events, a handler can also set the optional if field: one permission rule, such as "Bash(git *)" or "Edit(*.ts)", matched against the tool name and its arguments together. The hook only runs on a match:

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

The five hook handler types

When an event fires, its matching hooks run in parallel. Each handler object in the inner hooks array sets "type" to one of five options:

  • Command ("command"): runs a shell command. The script receives the event's JSON on stdin and replies through exit codes and stdout.
  • HTTP ("http"): sends the event's JSON as an HTTP POST request to a URL. The endpoint replies in its response body, using the same JSON output format as command hooks.
  • MCP tool ("mcp_tool"): calls a tool on a configured MCP server. Claude Code treats the tool's text output like command-hook stdout.
  • Prompt ("prompt"): sends a prompt to a Claude model for single-turn evaluation.
  • Agent ("agent"): spawns a subagent that can use tools such as Read, Grep and Glob to verify conditions before returning a decision. Agent hooks are experimental.

Default timeouts are 600 seconds for command, http and mcp_tool hooks, 30 seconds for prompt hooks and 60 seconds for agent hooks.

JSON input on stdin, exit codes and structured output

Every hook receives common JSON fields, such as session_id, transcript_path, cwd, permission_mode and hook_event_name, alongside event-specific data such as tool_name and tool_input. Inside a subagent, agent_id and agent_type tell your script which agent the hook fired in.

Command hooks return decisions through exit codes or structured JSON on stdout:

  • Exit code 0: reports no objection. On PreToolUse, exit 0 does not approve the call, so the normal permission flow still applies. On UserPromptSubmit, UserPromptExpansion, SessionStart and PostModelSwitch, plain-text stdout is added to Claude's context.
  • Exit code 2: blocks the action on events that can block, with stderr as the reason. On events that cannot be blocked, such as SessionStart, it shows stderr to the user and execution continues.
  • Structured JSON on stdout (with exit 0): gives finer control. On PreToolUse, hookSpecificOutput.permissionDecision accepts "allow", "deny", "ask" or "defer" (in non-interactive -p mode). A "deny" blocks the tool even in bypassPermissions mode.
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Destructive command blocked by hook"
  }
}

When several PreToolUse hooks disagree, precedence runs deny, defer, ask, allow. On PermissionRequest, returning hookSpecificOutput.decision.behavior set to "allow" grants the permission.

Watch Claude Code sessions on a keypad or phone with Orbtile

To watch several sessions at once and answer prompts without switching windows, Orbtile's Claude Code plugin shows every session on a Logitech MX Creative Keypad, or on an Android phone (Android 8.0 or later) with Orbtile Mobile. Orbtile runs on a computer with macOS 14.0 Sonoma or later on Apple silicon, or Windows 11 (x64).

  • Plugin setup: install the Claude Code integration from Settings > Agents > Claude Code on a Mac, or with the Install button on Windows. Then restart any running Claude Code sessions once so they load the plugin.
  • Live state and words per key: each session key shows the session's name, state (such as Thinking, Tool, Permission, Question or Done) and latest words over an animated orb. Waiting sessions come first, and a tap on key 9 opens the first waiting session.
  • Approve or answer from the pad: for local Claude Code sessions, hold a waiting key to open Focus mode. Press Deny on key 7 to decline, or hold Allow on key 9 for 0.6 seconds to approve. Multiple-choice questions with up to four options are answered on keys 1, 2, 3 and 7, then you hold Send on key 9. Typed answers stay on your computer, and remote sessions are display-only.
  • Multi-agent view: the same pad also shows sessions from Codex, ZCode and the Antigravity desktop app, plus your own scripts through the Adapter API. See Build your own status display with Claude Code hooks and Approve agent tool calls without switching windows.

FAQ

Where are Claude Code hooks configured?

In ~/.claude/settings.json for all your projects, in .claude/settings.json to share them with a repository, or in .claude/settings.local.json for local overrides that stay out of git. Managed policy settings, plugins, skills and subagents can also define hooks.

How does a Claude Code hook block a tool call?

A command hook on PreToolUse can exit with code 2 and write the reason to standard error. Or it can exit with code 0 and print JSON that sets hookSpecificOutput.permissionDecision to deny, with a permissionDecisionReason.

How does Orbtile use Claude Code hooks?

Orbtile installs a Claude Code plugin from its Settings. The plugin streams session events and live text to Orbtile on your computer, which shows each session on a Logitech MX Creative Keypad or an Android phone running Orbtile Mobile. For local permission requests, you can press Deny or hold Allow on the pad.