Skip to content
claude-hooks-reference-2026 logo

Claude Hooks Reference 2026

claude-hooks-reference-2026

Complete reference for Claude Code hooks system (January 2026). Use when creating hooks, understanding hook events, matchers, exit codes, JSON output control, environment variables, plugin hooks, or implementing hook scripts.

SKILL.md

Full skill instructions

Claude Code Hooks System - Complete Reference (January 2026)

Hooks execute custom commands or prompts in response to Claude Code events. Use for automation, validation, formatting, and security.


All Hook Events

EventWhen FiredMatcher AppliesCommon Uses
PreToolUseBefore tool executionYesValidation, blocking
PermissionRequestWhen user shown permission dialogYesAuto-approval policies
PostToolUseAfter successful tool executionYesFormatting, linting
PostToolUseFailureAfter tool failsYesError handling
NotificationWhen Claude wants attentionYesCustom notifications
UserPromptSubmitUser submits promptNoInput validation
StopClaude finishes responseNoCleanup, final checks
SubagentStartWhen spawning a subagentNoSubagent initialization
SubagentStopSubagent (Task tool) completesNoResult validation
PreCompactBefore context compactionYesState backup
SetupRepository setup/​maintenanceYesOne-time operations
SessionStartSession begins or resumesYesEnvironment setup
SessionEndSession endsNoCleanup, persistence

Configuration

Configuration Locations (Precedence highest to lowest)

  1. Managed - managed-settings.json (enterprise)
  2. Local - .claude/​settings.local.json (gitignored)
  3. Project - .claude/​settings.json (shared via git)
  4. User - ~/​.claude/​settings.json (personal)
  5. Plugin - hooks/​hooks.json or frontmatter
  6. Capability - Skill/​Command/​Agent frontmatter

Note: Enterprise administrators can use allowManagedHooksOnly to block user, project, and plugin hooks.

Structure

Hooks are organized by matchers, where each matcher can have multiple hooks:

{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here"
          }
        ]
      }
    ]
  }
}

Fields:

  • matcher: Pattern to match tool names, case-sensitive (only for PreToolUse, PermissionRequest, PostToolUse)
    • Simple strings match exactly: Write matches only the Write tool
    • Supports regex: Edit|Write or Notebook.*
    • Use * to match all tools. Also accepts empty string ("") or omit matcher
  • hooks: Array of hooks to execute when pattern matches
    • type: "command" for bash commands or "prompt" for LLM evaluation
    • command: (For type: "command") The bash command to execute
    • prompt: (For type: "prompt") The prompt to send to the LLM
    • timeout: (Optional) Seconds before canceling (default: 60 for commands, 30 for prompts)

Project-Specific Hook Scripts

Use $CLAUDE_PROJECT_DIR to reference scripts in your project:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/​.claude/​hooks/​check-style.sh"
          }
        ]
      }
    ]
  }
}

Events Without Matchers

For UserPromptSubmit, Stop, SubagentStop, and SessionEnd, omit the matcher:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/​path/​to/​prompt-validator.py"
          }
        ]
      }
    ]
  }
}

Plugin Hooks

Plugins can provide hooks that integrate with user and project hooks. For complete plugin documentation including plugin.json schema, directory structure, and component integration, see ./​claude-plugins-reference-2026/​SKILL.md.

How Plugin Hooks Work

  • Plugin hooks defined in hooks/​hooks.json or custom path via hooks field in plugin.json
  • When plugin enabled, its hooks merge with user and project hooks
  • Multiple hooks from different sources can respond to same event
  • Plugin hooks run alongside custom hooks in parallel

Plugin Hook Configuration

Hooks can be configured in hooks/​hooks.json or inline in plugin.json:

{
  "description": "Automatic code formatting",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/​scripts/​format.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Reference in plugin.json:

{
  "name": "my-plugin",
  "hooks": "./​hooks/​hooks.json"
}

Or define inline:

{
  "name": "my-plugin",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/​scripts/​format.sh"
          }
        ]
      }
    ]
  }
}

Plugin Environment Variables

  • ${CLAUDE_PLUGIN_ROOT}: Absolute path to the plugin directory
  • ${CLAUDE_PROJECT_DIR}: Project root directory
  • All standard environment variables available

Hooks in Skills, Agents, and Slash Commands

Hooks can be defined in frontmatter. These are scoped to the component's lifecycle. For complete skill documentation, see ./​claude-skills-reference-2026/​SKILL.md.

Supported events: PreToolUse, PostToolUse, Stop

Skill Example

---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./​scripts/​security-check.sh"
---

Agent Example

---
name: code-reviewer
description: Review code changes
hooks:
  PostToolUse:
    - matcher: "Edit|Write"
      hooks:
        - type: command
          command: "./​scripts/​run-linter.sh"
---

once Option

Set once: true to run hook only once per session. After first successful execution, hook is removed.

hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./​scripts/​one-time-setup.sh"
          once: true

Note: Only supported for skills and slash commands, not agents.


Event-Specific Matchers

PreToolUse / PermissionRequest / PostToolUse Common Matchers

MatcherDescription
TaskSubagent tasks
BashShell commands
GlobFile pattern matching
GrepContent search
ReadFile reading
EditFile editing
WriteFile writing
WebFetchWeb fetching
WebSearchWeb search
Notebook.*NotebookEdit, NotebookRead
mcp__<server>__.*All tools from MCP server
mcp__.*__write.*MCP write tools across servers

SessionStart Matchers

MatcherTrigger
startupNew session started
resume--resume, --continue, or /​resume
clear/​clear command
compactAuto or manual compact

PreCompact Matchers

MatcherTrigger
manual/​compact command
autoAuto-compact (full context)

Setup Matchers

MatcherTrigger
init--init or --init-only flags
maintenance--maintenance flag

Notification Matchers

MatcherTrigger
permission_promptPermission requests from Claude
idle_promptClaude waiting for input (60s+ idle)
auth_successAuthentication success
elicitation_dialogMCP tool elicitation

Hook Input (JSON via stdin)

Common Fields (All Events)

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​current/​working/​directory",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse"
}

permission_mode values: "default", "plan", "acceptEdits", "dontAsk", "bypassPermissions"

PreToolUse Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "psql -c 'SELECT * FROM users'",
    "description": "Query the users table",
    "timeout": 120000
  },
  "tool_use_id": "toolu_01ABC123"
}

Tool-specific tool_input fields:

ToolFields
Bashcommand, description, timeout, run_in_background
Writefile_path, content
Editfile_path, old_string, new_string, replace_all
Readfile_path, offset, limit

PostToolUse Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/​path/​to/​file.txt",
    "content": "file content"
  },
  "tool_response": {
    "filePath": "/​path/​to/​file.txt",
    "success": true
  },
  "tool_use_id": "toolu_01ABC123"
}

Notification Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "Notification",
  "message": "Claude needs your permission to use Bash",
  "notification_type": "permission_prompt"
}

UserPromptSubmit Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Write a function to calculate factorial"
}

Stop Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "Stop",
  "stop_hook_active": true
}

stop_hook_active: True when Claude is already continuing due to a stop hook. Check this to prevent infinite loops.

SubagentStart Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "SubagentStart",
  "agent_id": "agent-abc123",
  "agent_type": "Explore"
}

Fields:

  • agent_id: Unique identifier for the subagent
  • agent_type: Agent name (built-in like "Bash", "Explore", "Plan", or custom agent names)

SubagentStop Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "SubagentStop",
  "stop_hook_active": false,
  "agent_id": "def456",
  "agent_transcript_path": "/​path/​to/​subagents/​agent-def456.jsonl"
}

Fields:

  • agent_id: Unique identifier for the subagent
  • agent_transcript_path: Path to the subagent's own transcript in nested subagents/ folder
  • stop_hook_active: True when already continuing due to a stop hook

PreCompact Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "PreCompact",
  "trigger": "manual",
  "custom_instructions": ""
}

Setup Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "Setup",
  "trigger": "init"
}

Fields:

  • trigger: Either "init" (from --init or --init-only) or "maintenance" (from --maintenance)
  • Setup hooks have access to CLAUDE_ENV_FILE for persisting environment variables

SessionStart Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "SessionStart",
  "source": "startup",
  "model": "claude-sonnet-4-20250514"
}

Fields:

  • source: Indicates how session started: "startup" (new), "resume" (resumed), "clear" (after /​clear), or "compact" (after compaction)
  • model: Model identifier when available
  • agent_type: (Optional) Present when Claude Code started with claude --agent <name>

SessionEnd Input

{
  "session_id": "abc123",
  "transcript_path": "/​path/​to/​session.jsonl",
  "cwd": "/​path/​to/​project",
  "permission_mode": "default",
  "hook_event_name": "SessionEnd",
  "reason": "exit"
}

reason values: clear, logout, prompt_input_exit, other


Hook Output

Exit Codes

CodeBehavior
0Success. stdout processed (JSON or plain text)
2Blocking error. stderr used as error message, fed back to Claude
OtherNon-blocking error. stderr shown in verbose mode (Ctrl+O)

Important: Claude Code does not see stdout if exit code is 0, except for UserPromptSubmit and SessionStart where stdout is added to context.

Exit Code 2 Behavior Per Event

EventExit Code 2 Behavior
PreToolUseBlocks tool call, shows stderr to Claude
PermissionRequestDenies permission, shows stderr to Claude
PostToolUseShows stderr to Claude (tool already ran)
PostToolUseFailureShows stderr to Claude (tool already failed)
NotificationShows stderr to user only
UserPromptSubmitBlocks prompt, erases it, shows stderr to user
StopBlocks stoppage, shows stderr to Claude
SubagentStartShows stderr to user only
SubagentStopBlocks stoppage, shows stderr to Claude subagent
PreCompactShows stderr to user only
SetupShows stderr to user only
SessionStartShows stderr to user only
SessionEndShows stderr to user only

JSON Output Control

Important: JSON output only processed with exit code 0. Exit code 2 uses stderr only.

Common JSON Fields (All Events)

{
  "continue": true,
  "stopReason": "Message shown when continue is false",
  "suppressOutput": false,
  "systemMessage": "Optional warning message shown to user"
}
FieldTypeEffect
continuebooleanfalse stops Claude (takes precedence over all)
stopReasonstringShown to user when continue is false
suppressOutputbooleanHide stdout from transcript mode
systemMessagestringWarning message shown to user

Precedence: continue: false takes precedence over any decision: "block" output.

PreToolUse JSON Output

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "Auto-approved documentation file",
    "updatedInput": {
      "field_to_modify": "new value"
    },
    "additionalContext": "Current environment: production. Proceed with caution."
  }
}
FieldValuesEffect
permissionDecisionallow, deny, askControls tool execution
permissionDecisionReasonstringShown to user (allow/​ask) or Claude (deny)
updatedInputobjectModifies tool input before execution
additionalContextstringAdded to Claude's context

Note: decision and reason fields are deprecated. Use hookSpecificOutput.permissionDecision and hookSpecificOutput.permissionDecisionReason. Deprecated "approve" and "block" map to "allow" and "deny".

PermissionRequest JSON Output

Allow with modified input:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {
        "command": "npm run lint"
      }
    }
  }
}

Deny with message:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "deny",
      "message": "Command not allowed by policy",
      "interrupt": true
    }
  }
}

PostToolUse JSON Output

{
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Additional information for Claude"
  }
}
  • "block" automatically prompts Claude with reason
  • undefined does nothing, reason is ignored

UserPromptSubmit JSON Output

{
  "decision": "block",
  "reason": "Prompt contains sensitive data",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here"
  }
}

Two ways to add context (exit code 0):

  1. Plain text stdout: Any non-JSON text is added as context
  2. JSON with additionalContext: More structured control

Blocking prompts:

  • "decision": "block" prevents prompt processing, erases prompt from context
  • "reason" shown to user but not added to context

Stop / SubagentStop JSON Output

{
  "decision": "block",
  "reason": "Must be provided when Claude is blocked from stopping"
}
  • "block" prevents Claude from stopping. Must populate reason
  • undefined allows Claude to stop. reason is ignored

Setup JSON Output

{
  "hookSpecificOutput": {
    "hookEventName": "Setup",
    "additionalContext": "Repository initialized with custom configuration"
  }
}

Note: Multiple hooks' additionalContext values are concatenated. Setup hooks have access to CLAUDE_ENV_FILE for persisting environment variables.

SessionStart JSON Output

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Project context loaded successfully"
  }
}

Note: Multiple hooks' additionalContext values are concatenated.


Prompt-Based Hooks

LLM-evaluated decisions using a fast model (Haiku). Also known as "agent hooks" for complex verification tasks.

How Prompt-Based Hooks Work

  1. Send the hook input and your prompt to Haiku
  2. The LLM responds with structured JSON containing a decision
  3. Claude Code processes the decision automatically

Configuration

{
  "type": "prompt",
  "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete.",
  "timeout": 30
}

Alternatively, use "type": "agent" for complex verification tasks that require tool access.

FieldRequiredDescription
typeYes"prompt" for LLM evaluation, "agent" for tools
promptYesPrompt text sent to LLM
timeoutNoSeconds (default: 30 for prompt, 60 for agent)

Response Schema

The LLM must respond with JSON:

{
  "ok": true,
  "reason": "Explanation for the decision"
}
FieldTypeDescription
okbooleantrue allows the action, false prevents it
reasonstringRequired when ok is false. Shown to Claude

$ARGUMENTS Placeholder

Use $ARGUMENTS in prompt to include hook input JSON. If omitted, input is appended to the prompt.

Example: Intelligent Stop Hook

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Example: SubagentStop Validation

{
  "hooks": {
    "SubagentStop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if this subagent should stop. Input: $ARGUMENTS\n\nCheck if:\n- The subagent completed its assigned task\n- Any errors occurred that need fixing\n- Additional context gathering is needed\n\nReturn: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"explanation\"} to continue."
          }
        ]
      }
    ]
  }
}

Best Use Cases

EventUse Case
StopIntelligent task completion detection
SubagentStopVerify subagent completed task
UserPromptSubmitContext-aware prompt validation
PreToolUseComplex permission decisions
PermissionRequestIntelligent allow/​deny dialogs

Comparison with Command Hooks

FeatureCommand HooksPrompt Hooks
ExecutionRuns bash scriptQueries LLM
Decision logicYou implement in codeLLM evaluates context
Setup complexityRequires script fileConfigure prompt only
Context awarenessLimited to scriptNatural language understanding
PerformanceFast (local)Slower (API call)
Use caseDeterministic rulesContext-aware decisions

Best Practices for Prompt Hooks

  1. Be specific in prompts - Clearly state what you want the LLM to evaluate
  2. Include decision criteria - List the factors the LLM should consider
  3. Test your prompts - Verify the LLM makes correct decisions for your use cases
  4. Set appropriate timeouts - Default is 30 seconds, adjust if needed
  5. Use for complex decisions - Bash hooks are better for simple, deterministic rules

Important Hook Events

Setup Hook

Runs when Claude Code is invoked with repository setup and maintenance flags (--init, --init-only, or --maintenance).

Use Setup hooks for:

  • One-time or occasional operations (dependency installation, migrations, cleanup)
  • Operations you don't want on every session start

Matchers:

  • init - Invoked from --init or --init-only flags
  • maintenance - Invoked from --maintenance flag

Key characteristics:

  • Requires explicit flags because running automatically would slow down every session start
  • Has access to CLAUDE_ENV_FILE for persisting environment variables
  • Output added to Claude's context

SessionStart Hook

Runs when Claude Code starts a new session or resumes an existing session.

Use SessionStart hooks for:

  • Loading development context (existing issues, recent changes)
  • Setting up environment variables

Important: For one-time operations like installing dependencies or running migrations, use Setup hooks instead. SessionStart runs on every session, so keep these hooks fast.

Matchers:

  • startup - New sessions
  • resume - Resumed sessions (from --resume, --continue, or /​resume)
  • clear - After /​clear command
  • compact - After auto or manual compact

PostToolUseFailure Hook

Runs immediately after a tool fails (returns an error). This complements PostToolUse, which only runs on successful tool execution.

Use PostToolUseFailure hooks for:

  • Error recovery actions
  • Logging tool failures
  • Custom error handling and reporting

Recognizes the same matcher values as PreToolUse and PostToolUse.

SubagentStart Hook

Runs when a Claude Code subagent (Task tool call) is spawned.

Use SubagentStart hooks for:

  • Subagent initialization
  • Logging subagent creation
  • Context injection for specific agent types

Input includes:

  • agent_id: Unique identifier for the subagent
  • agent_type: Agent name (built-in like "Bash", "Explore", "Plan", or custom agent names)

Environment Variables

VariableDescriptionAvailable In
CLAUDE_PROJECT_DIRProject root (absolute path)All hooks
CLAUDE_CODE_REMOTE"true" if remote, empty if localAll hooks
CLAUDE_ENV_FILEPath for persisting env varsSessionStart, Setup
CLAUDE_PLUGIN_ROOTPlugin directory (absolute)Plugin hooks

SessionStart Environment Persistence

Example: Setting individual environment variables

#!/​bin/​bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
  echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"
  echo 'export PATH="$PATH:./​node_modules/​.bin"' >> "$CLAUDE_ENV_FILE"
fi
exit 0

Example: Persisting all environment changes (e.g., nvm use)

#!/​bin/​bash
ENV_BEFORE=$(export -p | sort)

# Run setup commands that modify environment
source ~/​.nvm/​nvm.sh
nvm use 20

if [ -n "$CLAUDE_ENV_FILE" ]; then
  ENV_AFTER=$(export -p | sort)
  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi
exit 0

Variables in $CLAUDE_ENV_FILE are sourced before each Bash command.


Working with MCP Tools

Claude Code hooks work with Model Context Protocol (MCP) tools.

MCP Tool Naming

MCP tools follow the pattern mcp__<server>__<tool>:

  • mcp__memory__create_entities - Memory server's create entities tool
  • mcp__filesystem__read_file - Filesystem server's read file tool
  • mcp__github__search_repositories - GitHub server's search tool

Configuring Hooks for MCP Tools

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/​mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/​home/​user/​scripts/​validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

Execution Details

AspectBehavior
Timeout60 seconds default, configurable
ParallelizationAll matching hooks run in parallel
DeduplicationIdentical commands deduplicated
EnvironmentRuns in cwd with Claude Code's env
Hook orderHooks from all sources execute together

Output Handling by Event

Eventstdout Handling
UserPromptSubmit, SessionStart, SetupAdded to Claude's context
PreToolUse, PostToolUse, StopShown in verbose mode (Ctrl+O)
Notification, SessionEnd, SubagentStartLogged to debug only (--debug)

Security Considerations

Disclaimer

USE AT YOUR OWN RISK: Claude Code hooks execute arbitrary shell commands on your system automatically. By using hooks, you acknowledge that:

  • You are solely responsible for the commands you configure
  • Hooks can modify, delete, or access any files your user account can access
  • Malicious or poorly written hooks can cause data loss or system damage
  • Anthropic provides no warranty and assumes no liability for any damages
  • You should thoroughly test hooks in a safe environment before production use

Security Best Practices

  1. Validate and sanitize inputs - Never trust input data blindly
  2. Always quote shell variables - Use "$VAR" not $VAR
  3. Block path traversal - Check for .. in file paths
  4. Use absolute paths via the $CLAUDE_PROJECT_DIR variable - Specify full paths for scripts (use $CLAUDE_PROJECT_DIR)
  5. Skip sensitive files - Avoid .env, .git/, keys, etc.

Configuration Safety

Direct edits to hooks in settings files don't take effect immediately:

  1. Hooks snapshot captured at startup
  2. Snapshot used throughout the session
  3. Warns if hooks are modified externally
  4. Requires review in /​hooks menu for changes to apply

This prevents malicious hook modifications from affecting your current session.


Debugging

Enable Debug Mode

claude --debug
claude --debug "hooks"  # Filter to hooks only

Debug Output Example

[DEBUG] Executing hooks for PostToolUse:Write
[DEBUG] Getting matching hook commands for PostToolUse with query: Write
[DEBUG] Found 1 hook matchers in settings
[DEBUG] Matched 1 hooks for query "Write"
[DEBUG] Found 1 hook commands to execute
[DEBUG] Executing hook command: <Your command> with timeout 60000ms
[DEBUG] Hook command completed with status 0: <Your stdout>

Basic Troubleshooting

  1. Check configuration - Run /​hooks to see if your hook is registered
  2. Verify syntax - Ensure your JSON settings are valid
  3. Test commands - Run hook commands manually first
  4. Check permissions - Make sure scripts are executable
  5. Review logs - Use claude --debug to see hook execution details
  6. Validate plugin hooks - Use claude plugin validate or /​plugin validate for plugin-level hooks

Common Issues

ProblemCauseFix
Hook not runningWrong matcher patternCheck case-sensitivity, regex
Command not foundRelative pathUse $CLAUDE_PROJECT_DIR
JSON not processedNon-zero exit codeExit 0 for JSON processing
Hook times outSlow scriptOptimize or increase timeout
Quotes breakingUnescaped in JSONUse \" inside JSON strings
Plugin hook not loadInvalid plugin.json hooks configValidate with claude plugin validate .
Path not foundMissing ${CLAUDE_PLUGIN_ROOT}Use variable for plugin scripts

Validation Commands

For plugin hooks:

# CLI (from terminal)
claude plugin validate .
claude plugin validate ./​path/​to/​plugin

# In Claude Code session
/​plugin validate .
/​plugin validate ./​path/​to/​plugin

For settings hooks: JSON validation with:

python3 -m json.tool .claude/​settings.json

Test Commands Manually

echo '{"tool_name":"Write","tool_input":{"file_path":"test.txt"}}' | ./​your-hook.sh

Code Examples

Python: Bash Command Validation (Exit Code)

#!/​usr/​bin/​env python3
import json
import re
import sys

# Define validation rules as (regex pattern, message) tuples
VALIDATION_RULES = [
    (
        r"\bgrep\b(?!.*\|)",
        "Use 'rg' (ripgrep) instead of 'grep' for better performance",
    ),
    (
        r"\bfind\s+\S+\s+-name\b",
        "Use 'rg --files -g pattern' instead of 'find -name'",
    ),
]

def validate_command(command: str) -> list[str]:
    issues = []
    for pattern, message in VALIDATION_RULES:
        if re.search(pattern, command):
            issues.append(message)
    return issues

try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
command = tool_input.get("command", "")

if tool_name != "Bash" or not command:
    sys.exit(0)

issues = validate_command(command)

if issues:
    for message in issues:
        print(f"• {message}", file=sys.stderr)
    # Exit code 2 blocks tool call and shows stderr to Claude
    sys.exit(2)

Python: UserPromptSubmit Context and Validation (JSON Output)

#!/​usr/​bin/​env python3
import json
import sys
import re
import datetime

try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

prompt = input_data.get("prompt", "")

# Check for sensitive patterns
sensitive_patterns = [
    (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
]

for pattern, message in sensitive_patterns:
    if re.search(pattern, prompt):
        # Use JSON output to block with a specific reason
        output = {
            "decision": "block",
            "reason": f"Security policy violation: {message}. Please rephrase without sensitive information."
        }
        print(json.dumps(output))
        sys.exit(0)

# Add current time to context
context = f"Current time: {datetime.datetime.now()}"
print(context)

# Equivalent JSON approach:
# print(json.dumps({
#   "hookSpecificOutput": {
#     "hookEventName": "UserPromptSubmit",
#     "additionalContext": context,
#   },
# }))

sys.exit(0)

Python: PreToolUse Auto-Approval (JSON Output)

#!/​usr/​bin/​env python3
import json
import sys

try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})

# Auto-approve file reads for documentation files
if tool_name == "Read":
    file_path = tool_input.get("file_path", "")
    if file_path.endswith((".md", ".mdx", ".txt", ".json")):
        output = {
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "allow",
                "permissionDecisionReason": "Documentation file auto-approved"
            },
            "suppressOutput": True
        }
        print(json.dumps(output))
        sys.exit(0)

# Let normal permission flow proceed
sys.exit(0)

Node.js: SessionStart Context Injection

#!/​usr/​bin/​env node

const output = {
  hookSpecificOutput: {
    hookEventName: "SessionStart",
    additionalContext: `<project-context>
Environment: ${process.env.NODE_ENV || "development"}
Node version: ${process.version}
Working directory: ${process.cwd()}
</​project-context>`,
  },
};

console.log(JSON.stringify(output));

Examples

Code Formatting (PostToolUse)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "prettier --write \"$CLAUDE_PROJECT_DIR\"/​**/​*.{js,ts,json}"
          }
        ]
      }
    ]
  }
}

File Protection (PreToolUse)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "./​scripts/​check-protected-files.sh"
          }
        ]
      }
    ]
  }
}

Custom Notifications

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/​path/​to/​permission-alert.sh"
          }
        ]
      },
      {
        "matcher": "idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/​path/​to/​idle-notification.sh"
          }
        ]
      }
    ]
  }
}

Task Verification (Stop)

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Verify task completion. Check edge cases. Return {\"ok\": true} or {\"ok\": false, \"reason\": \"...\"}."
          }
        ]
      }
    ]
  }
}

Sources

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View