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.
Automates custom scripts or LLM prompts in response to Claude Code events for validation, formatting, and security.
/plugin marketplace add jamie-bitflight/claude_skills/plugin install plugin-creator@jamie-bitflight-skillsclaude-hooks-reference-2026/Hooks execute custom commands or prompts in response to Claude Code events. Use for automation, validation, formatting, and security.
| Event | When Fired | Matcher Applies | Common Uses |
|---|---|---|---|
PreToolUse | Before tool execution | Yes | Validation, blocking |
PermissionRequest | When user shown permission dialog | Yes | Auto-approval policies |
PostToolUse | After successful tool execution | Yes | Formatting, linting |
PostToolUseFailure | After tool fails | Yes | Error handling |
Notification | When Claude wants attention | Yes | Custom notifications |
UserPromptSubmit | User submits prompt | No | Input validation |
Stop | Claude finishes response | No | Cleanup, final checks |
SubagentStart | When spawning a subagent | No | Subagent initialization |
SubagentStop | Subagent (Task tool) completes | No | Result validation |
PreCompact | Before context compaction | Yes | State backup |
Setup | Repository setup/maintenance | Yes | One-time operations |
SessionStart | Session begins or resumes | Yes | Environment setup |
SessionEnd | Session ends | No | Cleanup, persistence |
managed-settings.json (enterprise).claude/settings.local.json (gitignored).claude/settings.json (shared via git)~/.claude/settings.json (personal)hooks/hooks.json or frontmatterNote: Enterprise administrators can use allowManagedHooksOnly to block user, project, and plugin hooks.
Hooks are organized by matchers, where each matcher can have multiple hooks:
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here"
}
]
}
]
}
}
Fields:
PreToolUse, PermissionRequest, PostToolUse)
Write matches only the Write toolEdit|Write or Notebook.** to match all tools. Also accepts empty string ("") or omit matchertype: "command" for bash commands or "prompt" for LLM evaluationcommand: (For type: "command") The bash command to executeprompt: (For type: "prompt") The prompt to send to the LLMtimeout: (Optional) Seconds before canceling (default: 60 for commands, 30 for prompts)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"
}
]
}
]
}
}
For UserPromptSubmit, Stop, SubagentStop, and SessionEnd, omit the matcher:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "/path/to/prompt-validator.py"
}
]
}
]
}
}
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.
hooks/hooks.json or custom path via hooks field in plugin.jsonHooks 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"
}
]
}
]
}
}
${CLAUDE_PLUGIN_ROOT}: Absolute path to the plugin directory${CLAUDE_PROJECT_DIR}: Project root directoryHooks 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
---
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---
---
description: Review code changes
hooks:
PostToolUse:
- matcher: "Edit|Write"
hooks:
- type: command
command: "./scripts/run-linter.sh"
---
once OptionSet 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.
| Matcher | Description |
|---|---|
Task | Subagent tasks |
Bash | Shell commands |
Glob | File pattern matching |
Grep | Content search |
Read | File reading |
Edit | File editing |
Write | File writing |
WebFetch | Web fetching |
WebSearch | Web search |
Notebook.* | NotebookEdit, NotebookRead |
mcp__<server>__.* | All tools from MCP server |
mcp__.*__write.* | MCP write tools across servers |
| Matcher | Trigger |
|---|---|
startup | New session started |
resume | --resume, --continue, or /resume |
clear | /clear command |
compact | Auto or manual compact |
| Matcher | Trigger |
|---|---|
manual | /compact command |
auto | Auto-compact (full context) |
| Matcher | Trigger |
|---|---|
init | --init or --init-only flags |
maintenance | --maintenance flag |
| Matcher | Trigger |
|---|---|
permission_prompt | Permission requests from Claude |
idle_prompt | Claude waiting for input (60s+ idle) |
auth_success | Authentication success |
elicitation_dialog | MCP tool elicitation |
{
"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"
{
"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:
| Tool | Fields |
|---|---|
Bash | command, description, timeout, run_in_background |
Write | file_path, content |
Edit | file_path, old_string, new_string, replace_all |
Read | file_path, offset, limit |
{
"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"
}
{
"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"
}
{
"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"
}
{
"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.
{
"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 subagentagent_type: Agent name (built-in like "Bash", "Explore", "Plan", or custom agent names){
"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 subagentagent_transcript_path: Path to the subagent's own transcript in nested subagents/ folderstop_hook_active: True when already continuing due to a stop hook{
"session_id": "abc123",
"transcript_path": "/path/to/session.jsonl",
"cwd": "/path/to/project",
"permission_mode": "default",
"hook_event_name": "PreCompact",
"trigger": "manual",
"custom_instructions": ""
}
{
"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)CLAUDE_ENV_FILE for persisting environment variables{
"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 availableagent_type: (Optional) Present when Claude Code started with claude --agent <name>{
"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
| Code | Behavior |
|---|---|
| 0 | Success. stdout processed (JSON or plain text) |
| 2 | Blocking error. stderr used as error message, fed back to Claude |
| Other | Non-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.
| Event | Exit Code 2 Behavior |
|---|---|
PreToolUse | Blocks tool call, shows stderr to Claude |
PermissionRequest | Denies permission, shows stderr to Claude |
PostToolUse | Shows stderr to Claude (tool already ran) |
PostToolUseFailure | Shows stderr to Claude (tool already failed) |
Notification | Shows stderr to user only |
UserPromptSubmit | Blocks prompt, erases it, shows stderr to user |
Stop | Blocks stoppage, shows stderr to Claude |
SubagentStart | Shows stderr to user only |
SubagentStop | Blocks stoppage, shows stderr to Claude subagent |
PreCompact | Shows stderr to user only |
Setup | Shows stderr to user only |
SessionStart | Shows stderr to user only |
SessionEnd | Shows stderr to user only |
Important: JSON output only processed with exit code 0. Exit code 2 uses stderr only.
{
"continue": true,
"stopReason": "Message shown when continue is false",
"suppressOutput": false,
"systemMessage": "Optional warning message shown to user"
}
| Field | Type | Effect |
|---|---|---|
continue | boolean | false stops Claude (takes precedence over all) |
stopReason | string | Shown to user when continue is false |
suppressOutput | boolean | Hide stdout from transcript mode |
systemMessage | string | Warning message shown to user |
Precedence: continue: false takes precedence over any decision: "block" output.
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Auto-approved documentation file",
"updatedInput": {
"field_to_modify": "new value"
},
"additionalContext": "Current environment: production. Proceed with caution."
}
}
| Field | Values | Effect |
|---|---|---|
permissionDecision | allow, deny, ask | Controls tool execution |
permissionDecisionReason | string | Shown to user (allow/ask) or Claude (deny) |
updatedInput | object | Modifies tool input before execution |
additionalContext | string | Added 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".
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
}
}
}
{
"decision": "block",
"reason": "Explanation for decision",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Additional information for Claude"
}
}
"block" automatically prompts Claude with reasonundefined does nothing, reason is ignored{
"decision": "block",
"reason": "Prompt contains sensitive data",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "My additional context here"
}
}
Two ways to add context (exit code 0):
additionalContext: More structured controlBlocking prompts:
"decision": "block" prevents prompt processing, erases prompt from context"reason" shown to user but not added to context{
"decision": "block",
"reason": "Must be provided when Claude is blocked from stopping"
}
"block" prevents Claude from stopping. Must populate reasonundefined allows Claude to stop. reason is ignored{
"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.
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Project context loaded successfully"
}
}
Note: Multiple hooks' additionalContext values are concatenated.
LLM-evaluated decisions using a fast model (Haiku). Also known as "agent hooks" for complex verification tasks.
{
"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.
| Field | Required | Description |
|---|---|---|
type | Yes | "prompt" for LLM evaluation, "agent" for tools |
prompt | Yes | Prompt text sent to LLM |
timeout | No | Seconds (default: 30 for prompt, 60 for agent) |
The LLM must respond with JSON:
{
"ok": true,
"reason": "Explanation for the decision"
}
| Field | Type | Description |
|---|---|---|
ok | boolean | true allows the action, false prevents it |
reason | string | Required when ok is false. Shown to Claude |
Use $ARGUMENTS in prompt to include hook input JSON. If omitted, input is appended to the prompt.
{
"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
}
]
}
]
}
}
{
"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."
}
]
}
]
}
}
| Event | Use Case |
|---|---|
Stop | Intelligent task completion detection |
SubagentStop | Verify subagent completed task |
UserPromptSubmit | Context-aware prompt validation |
PreToolUse | Complex permission decisions |
PermissionRequest | Intelligent allow/deny dialogs |
| Feature | Command Hooks | Prompt Hooks |
|---|---|---|
| Execution | Runs bash script | Queries LLM |
| Decision logic | You implement in code | LLM evaluates context |
| Setup complexity | Requires script file | Configure prompt only |
| Context awareness | Limited to script | Natural language understanding |
| Performance | Fast (local) | Slower (API call) |
| Use case | Deterministic rules | Context-aware decisions |
Runs when Claude Code is invoked with repository setup and maintenance flags (--init, --init-only, or --maintenance).
Use Setup hooks for:
Matchers:
init - Invoked from --init or --init-only flagsmaintenance - Invoked from --maintenance flagKey characteristics:
CLAUDE_ENV_FILE for persisting environment variablesRuns when Claude Code starts a new session or resumes an existing session.
Use SessionStart hooks for:
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 sessionsresume - Resumed sessions (from --resume, --continue, or /resume)clear - After /clear commandcompact - After auto or manual compactRuns immediately after a tool fails (returns an error). This complements PostToolUse, which only runs on successful tool execution.
Use PostToolUseFailure hooks for:
Recognizes the same matcher values as PreToolUse and PostToolUse.
Runs when a Claude Code subagent (Task tool call) is spawned.
Use SubagentStart hooks for:
Input includes:
agent_id: Unique identifier for the subagentagent_type: Agent name (built-in like "Bash", "Explore", "Plan", or custom agent names)| Variable | Description | Available In |
|---|---|---|
CLAUDE_PROJECT_DIR | Project root (absolute path) | All hooks |
CLAUDE_CODE_REMOTE | "true" if remote, empty if local | All hooks |
CLAUDE_ENV_FILE | Path for persisting env vars | SessionStart, Setup |
CLAUDE_PLUGIN_ROOT | Plugin directory (absolute) | Plugin hooks |
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.
Claude Code hooks work with Model Context Protocol (MCP) tools.
MCP tools follow the pattern mcp__<server>__<tool>:
mcp__memory__create_entities - Memory server's create entities toolmcp__filesystem__read_file - Filesystem server's read file toolmcp__github__search_repositories - GitHub server's search tool{
"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"
}
]
}
]
}
}
| Aspect | Behavior |
|---|---|
| Timeout | 60 seconds default, configurable |
| Parallelization | All matching hooks run in parallel |
| Deduplication | Identical commands deduplicated |
| Environment | Runs in cwd with Claude Code's env |
| Hook order | Hooks from all sources execute together |
| Event | stdout Handling |
|---|---|
| UserPromptSubmit, SessionStart, Setup | Added to Claude's context |
| PreToolUse, PostToolUse, Stop | Shown in verbose mode (Ctrl+O) |
| Notification, SessionEnd, SubagentStart | Logged to debug only (--debug) |
USE AT YOUR OWN RISK: Claude Code hooks execute arbitrary shell commands on your system automatically. By using hooks, you acknowledge that:
"$VAR" not $VAR.. in file paths$CLAUDE_PROJECT_DIR).env, .git/, keys, etc.Direct edits to hooks in settings files don't take effect immediately:
/hooks menu for changes to applyThis prevents malicious hook modifications from affecting your current session.
claude --debug
claude --debug "hooks" # Filter to hooks only
[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>
/hooks to see if your hook is registeredclaude --debug to see hook execution detailsclaude plugin validate or /plugin validate for plugin-level hooks| Problem | Cause | Fix |
|---|---|---|
| Hook not running | Wrong matcher pattern | Check case-sensitivity, regex |
| Command not found | Relative path | Use $CLAUDE_PROJECT_DIR |
| JSON not processed | Non-zero exit code | Exit 0 for JSON processing |
| Hook times out | Slow script | Optimize or increase timeout |
| Quotes breaking | Unescaped in JSON | Use \" inside JSON strings |
| Plugin hook not load | Invalid plugin.json hooks config | Validate with claude plugin validate . |
| Path not found | Missing ${CLAUDE_PLUGIN_ROOT} | Use variable for plugin scripts |
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
echo '{"tool_name":"Write","tool_input":{"file_path":"test.txt"}}' | ./your-hook.sh
#!/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)
#!/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)
#!/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)
#!/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));
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$CLAUDE_PROJECT_DIR\"/**/*.{js,ts,json}"
}
]
}
]
}
}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "./scripts/check-protected-files.sh"
}
]
}
]
}
}
{
"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"
}
]
}
]
}
}
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Verify task completion. Check edge cases. Return {\"ok\": true} or {\"ok\": false, \"reason\": \"...\"}."
}
]
}
]
}
}