参考
Hooks 参考
Hooks 的参考表,查配置和字段用。
页面信息
这页不是官方原文,而是顺着官方文档结构做的中文解释版。命令、参数、配置名这些硬东西尽量保留,解释部分则尽量讲成人能照着做的话。
如果你碰到特别敏感的配置、权限或企业环境差异,最好顺手点上面的“查看原始文档”再核一遍。
这一页先讲明白
这页是 Hooks 的查表页,重点是你要知道有哪些字段和规则可用。
适合真正要写 hooks 配置时翻着用。
它像门禁规则说明书,不一定天天读,但到真要配的时候特别关键。
这一页更偏参考手册,不是闲聊型内容。
你需要的是准确,不是花哨。
写 hooks 前先把触发时机和输入输出想清楚。
先从最简单、最稳的钩子开始,不要一上来写一大串复杂逻辑。
查字段和例子时,这页最好和 hooks-guide 一起看。
原页关键片段:How a hook resolves 1
这一段说完,最后还得写到配置里才算真的生效。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
}
]
}
]
}
} 原页关键片段:How a hook resolves 2
这一段不是只让你理解意思,下面这条命令就是现在要跑的。
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0 # allow the command
fi 原页关键片段:How a hook resolves 3
这一段说完,最后还得写到配置里才算真的生效。
{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... } 原页关键片段:Matcher patterns 1
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/path/to/lint-check.sh"
}
]
}
]
}
} 原页关键片段:Matcher patterns 2
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"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"
}
]
}
]
}
} 原页关键片段:Hook handler fields 1
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
} 原页关键片段:Hook handler fields 2
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "my_server",
"tool": "security_scan",
"input": { "file_path": "${tool_input.file_path}" }
}
]
}
]
}
} 原页关键片段:Reference scripts by path 1
这一段说完,最后还得写到配置里才算真的生效。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
}
]
}
]
}
} 原页关键片段:Reference scripts by path 2
这一段说完,最后还得写到配置里才算真的生效。
{
"description": "Automatic code formatting",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
"timeout": 30
}
]
}
]
}
} 原页关键片段:Hooks in skills and agents
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
--- 原页关键片段:Common input fields
这会儿轮到改配置了,字段名和关键字别自己乱换。
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
} 原页关键片段:Exit code output
真到动手的时候了,下面这条直接敲一遍,看它回什么。
#!/bin/bash
# Reads JSON input from stdin, checks the command
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm* ]]; then
echo "Blocked: rm commands are not allowed" >&2
exit 2 # Blocking error: tool call is prevented
fi
exit 0 # Success: tool call proceeds Documentation Index
这里不是让你背"Documentation Index"这个词,而是让你看它真干活时怎么使。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
Hook lifecycle
这一块主要是在说"Hook lifecycle"真到手上该怎么用,哪里最容易踩坑。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
How a hook resolves
看到这里,就把"How a hook resolves"当成一件真要上手的活来看。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
How a hook resolves 1
这一段说完,最后还得写到配置里才算真的生效。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
}
]
}
]
}
} How a hook resolves 2
这一段不是只让你理解意思,下面这条命令就是现在要跑的。
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0 # allow the command
fi How a hook resolves 3
这一段说完,最后还得写到配置里才算真的生效。
{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... } How a hook resolves 4
这一段说完,最后还得写到配置里才算真的生效。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
} Configuration
这一段主要是在把"Configuration"讲实,不是只摆个标题给你看。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
Hook locations
这一块主要是在说"Hook locations"真到手上该怎么用,哪里最容易踩坑。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
Matcher patterns
这一段不只是挂个标题,它是在说明"Matcher patterns"这一块到底负责什么。
如果这里反复提 lead 和 teammate,通常是在提醒主代理别抢队友还没做完的活,先等人把结果交回来再继续统筹。
Matcher patterns 1
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/path/to/lint-check.sh"
}
]
}
]
}
} Matcher patterns 2
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"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"
}
]
}
]
}
} Hook handler fields
这种内容最怕想当然,老老实实照它列的格式走最稳。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
Hook handler fields 1
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
} Hook handler fields 2
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "my_server",
"tool": "security_scan",
"input": { "file_path": "${tool_input.file_path}" }
}
]
}
]
}
} Reference scripts by path
看到这里,就别靠猜了,字段、格式和写法都得按说明来。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
Reference scripts by path 1
这一段说完,最后还得写到配置里才算真的生效。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
}
]
}
]
}
} Reference scripts by path 2
这一段说完,最后还得写到配置里才算真的生效。
{
"description": "Automatic code formatting",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
"timeout": 30
}
]
}
]
}
} Hooks in skills and agents
这里不是让你背"Hooks in skills and agents"这个词,而是让你看它真干活时怎么使。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
Hooks in skills and agents
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
--- The /hooks menu
这一块主要是在说"The /hooks menu"真到手上该怎么用,哪里最容易踩坑。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
Disable or remove hooks
这里讲怎么把某个开关拧对。重点不是概念,而是你该在哪儿改、改完怎么确认生效。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
Hook input and output
这一块主要是在说"Hook input and output"真到手上该怎么用,哪里最容易踩坑。
如果你打算把外接能力往里挂,这里提到的 hooks、MCP、skills、memory 都要分清各自负责哪一摊。
Common input fields
这种内容最怕想当然,老老实实照它列的格式走最稳。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
Common input fields
这会儿轮到改配置了,字段名和关键字别自己乱换。
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
} Exit code output
这里主要是在交代"Exit code output"这一块会碰到哪些事。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
Exit code output
真到动手的时候了,下面这条直接敲一遍,看它回什么。
#!/bin/bash
# Reads JSON input from stdin, checks the command
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm* ]]; then
echo "Blocked: rm commands are not allowed" >&2
exit 2 # Blocking error: tool call is prevented
fi
exit 0 # Success: tool call proceeds HTTP response handling
这一段主要是在把"HTTP response handling"讲实,不是只摆个标题给你看。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
JSON output
看到这里,就把"JSON output"当成一件真要上手的活来看。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
JSON output 1
这一段说完,最后还得写到配置里才算真的生效。
{ "continue": false, "stopReason": "Build failed, fix errors before continuing" } JSON output 2
这一段说完,最后还得写到配置里才算真的生效。
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
}
} JSON output 3
这一段说完,最后还得写到配置里才算真的生效。
{
"decision": "block",
"reason": "Test suite must pass before proceeding"
} JSON output 4
这一段说完,最后还得写到配置里才算真的生效。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database writes are not allowed"
}
} Hook events
这里不是让你背"Hook events"这个词,而是让你看它真干活时怎么使。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
SessionStart
这里不是让你背"SessionStart"这个词,而是让你看它真干活时怎么使。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
SessionStart 1
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SessionStart",
"source": "startup",
"model": "claude-sonnet-4-6"
} SessionStart 2
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2"
}
} SessionStart 3
真到动手的时候了,下面这条直接敲一遍,看它回什么。
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi
exit 0 SessionStart 4
真到动手的时候了,下面这条直接敲一遍,看它回什么。
#!/bin/bash
ENV_BEFORE=$(export -p | sort)
# Run your setup commands that modify the 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 Setup
这一块主要是在说"Setup"真到手上该怎么用,哪里最容易踩坑。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
Setup 1
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Setup",
"trigger": "init"
} Setup 2
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"hookSpecificOutput": {
"hookEventName": "Setup",
"additionalContext": "Dependencies installed: node_modules, .venv"
}
} InstructionsLoaded
这里不是让你背"InstructionsLoaded"这个词,而是让你看它真干活时怎么使。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
InstructionsLoaded
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "InstructionsLoaded",
"file_path": "/Users/my-project/CLAUDE.md",
"memory_type": "Project",
"load_reason": "session_start"
} UserPromptSubmit
这一块主要是在说"UserPromptSubmit"真到手上该怎么用,哪里最容易踩坑。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
UserPromptSubmit 1
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "UserPromptSubmit",
"prompt": "Write a function to calculate the factorial of a number"
} UserPromptSubmit 2
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"decision": "block",
"reason": "Explanation for decision",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "My additional context here",
"sessionTitle": "My session title"
}
} UserPromptExpansion
这里不是让你背"UserPromptExpansion"这个词,而是让你看它真干活时怎么使。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
UserPromptExpansion 1
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"session_id": "abc123",
"transcript_path": "/Users/.../00893aaf.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "UserPromptExpansion",
"expansion_type": "slash_command",
"command_name": "example-skill",
"command_args": "arg1 arg2",
"command_source": "plugin",
"prompt": "/example-skill arg1 arg2"
} UserPromptExpansion 2
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"decision": "block",
"reason": "This slash command is not available",
"hookSpecificOutput": {
"hookEventName": "UserPromptExpansion",
"additionalContext": "Additional context for this expansion"
}
} PreToolUse
这里不是让你背"PreToolUse"这个词,而是让你看它真干活时怎么使。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
PreToolUse 1
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "My reason here",
"updatedInput": {
"field_to_modify": "new value"
},
"additionalContext": "Current environment: production. Proceed with caution."
}
} PreToolUse 2
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"type": "result",
"subtype": "success",
"stop_reason": "tool_deferred",
"session_id": "abc123",
"deferred_tool_use": {
"id": "toolu_01abc",
"name": "AskUserQuestion",
"input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
}
} PermissionRequest
这里不是让你背"PermissionRequest"这个词,而是让你看它真干活时怎么使。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
PermissionRequest 1
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PermissionRequest",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules",
"description": "Remove node_modules directory"
},
"permission_suggestions": [
{
"type": "addRules",
"rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
"behavior": "allow",
"destination": "localSettings"
}
]
} PermissionRequest 2
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
} PostToolUse
这一块主要是在说"PostToolUse"真到手上该怎么用,哪里最容易踩坑。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
PostToolUse 1
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"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...",
"duration_ms": 12
} PostToolUse 2
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Additional information for Claude",
"updatedToolOutput": {
"stdout": "[redacted]",
"stderr": "",
"interrupted": false,
"isImage": false
}
}
} PostToolUseFailure
这一块主要是在说"PostToolUseFailure"真到手上该怎么用,哪里最容易踩坑。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
PostToolUseFailure 1
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolUseFailure",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite"
},
"tool_use_id": "toolu_01ABC123...",
"error": "Command exited with non-zero status code 1",
"is_interrupt": false,
"duration_ms": 4187
} PostToolUseFailure 2
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"hookSpecificOutput": {
"hookEventName": "PostToolUseFailure",
"additionalContext": "Additional information about the failure for Claude"
}
} PostToolBatch
这里不是让你背"PostToolBatch"这个词,而是让你看它真干活时怎么使。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
PostToolBatch 1
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolBatch",
"tool_calls": [
{
"tool_name": "Read",
"tool_input": {"file_path": "/.../ledger/accounts.py"},
"tool_use_id": "toolu_01...",
"tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
},
{
"tool_name": "Read",
"tool_input": {"file_path": "/.../ledger/transactions.py"},
"tool_use_id": "toolu_02...",
"tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
}
]
} PostToolBatch 2
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"hookSpecificOutput": {
"hookEventName": "PostToolBatch",
"additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
}
} PermissionDenied
这里不是让你背"PermissionDenied"这个词,而是让你看它真干活时怎么使。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
PermissionDenied 1
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "auto",
"hook_event_name": "PermissionDenied",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/build",
"description": "Clean build directory"
},
"tool_use_id": "toolu_01ABC123...",
"reason": "Auto mode denied: command targets a path outside the project"
} PermissionDenied 2
光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。
{
"hookSpecificOutput": {
"hookEventName": "PermissionDenied",
"retry": true
}
} Notification
看到这里,就把"Notification"当成一件真要上手的活来看。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
Notification 1
这一段说完,最后还得写到配置里才算真的生效。
{
"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"
}
]
}
]
}
} Notification 2
这一段说完,最后还得写到配置里才算真的生效。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Notification",
"message": "Claude needs your permission to use Bash",
"title": "Permission needed",
"notification_type": "permission_prompt"
} SubagentStart
看到这里,就把"SubagentStart"当成一件真要上手的活来看。
看这段时要特别盯工具和权限边界,别为了省事一把全开。
SubagentStart 1
这一段说完,最后还得写到配置里才算真的生效。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SubagentStart",
"agent_id": "agent-abc123",
"agent_type": "Explore"
} SubagentStart 2
这一段说完,最后还得写到配置里才算真的生效。
{
"hookSpecificOutput": {
"hookEventName": "SubagentStart",
"additionalContext": "Follow security guidelines for this task"
}
} SubagentStop
看到这里,就把"SubagentStop"当成一件真要上手的活来看。
这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。
SubagentStop
这一段说完,最后还得写到配置里才算真的生效。
{
"session_id": "abc123",
"transcript_path": "~/.claude/projects/.../abc123.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "SubagentStop",
"stop_hook_active": false,
"agent_id": "def456",
"agent_type": "Explore",
"agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
"last_assistant_message": "Analysis complete. Found 3 potential issues..."
} 照着做一遍
如果你不想来回翻,就先照这几步顺着做。
每做完一步就看一下结果,再决定要不要继续往下。
第 1 步:How a hook resolves 1
这一段说完,最后还得写到配置里才算真的生效。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
}
]
}
]
}
} 第 2 步:How a hook resolves 2
这一段不是只让你理解意思,下面这条命令就是现在要跑的。
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0 # allow the command
fi 第 3 步:How a hook resolves 3
这一段说完,最后还得写到配置里才算真的生效。
{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... } 第 4 步:Matcher patterns 1
想把这条规矩固定住,就把下面这块老老实实写进去。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/path/to/lint-check.sh"
}
]
}
]
}
} 一眼看懂这一页
先把这页到底在讲什么看明白,再去碰具体命令和配置,最不容易绕晕。
Hooks reference
|
v
这是 Reference 里的一摊要紧活
|
v
先弄懂,再下手 文末提醒
这站会按官方 docs 的导航和内容变化继续重生成,原站加页、删页、改页时,这里会跟着更新。
人话解释会尽量顺着原页往下讲,但命令、参数名、配置名这些硬东西还是保留原样,免得你抄过去跑不起来。