钩子(Hooks)
钩子是在 Claude Code 会话中特定事件触发时自动执行的脚本,可实现自动化、校验、权限管理和自定义工作流。
概述
钩子是自动执行的动作(Shell 命令、HTTP Webhook、LLM 提示词或子代理评估),在 Claude Code 中特定事件发生时自动触发。钩子通过 stdin 接收 JSON 输入,通过退出码和 JSON 输出传递结果。
核心特性:
- 事件驱动自动化
- 基于 JSON 的输入/输出
- 支持 command、prompt、http、agent 四种钩子类型
- 工具特定钩子的模式匹配
配置
钩子在设置文件中配置:
~/.claude/settings.json—— 用户设置(所有项目).claude/settings.json—— 项目设置(可共享,可提交).claude/settings.local.json—— 本地项目设置(不提交)- 托管策略 —— 组织级设置
- 插件
hooks/hooks.json—— 插件作用域的钩子 - Skill/Agent frontmatter —— 组件生命周期钩子
基本配置结构
json
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here",
"timeout": 60
}
]
}
]
}
}关键字段:
| 字段 | 说明 | 示例 |
|---|---|---|
matcher | 匹配工具名称的模式(区分大小写) | "Write"、"Edit|Write"、"*" |
hooks | 钩子定义数组 | [{ "type": "command", ... }] |
type | 钩子类型:"command"(bash)、"prompt"(LLM)、"http"(webhook)或 "agent"(子代理) | "command" |
command | 要执行的 Shell 命令 | "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh" |
timeout | 可选超时时间(秒,默认 60) | 30 |
once | 若为 true,每次会话只运行一次 | true |
匹配模式
| 模式 | 说明 | 示例 |
|---|---|---|
| 精确字符串 | 匹配特定工具 | "Write" |
| 正则表达式 | 匹配多个工具 | "Edit|Write" |
| 通配符 | 匹配所有工具 | "*" 或 "" |
| MCP 工具 | 服务器和工具模式 | "mcp__memory__.*" |
钩子类型
Claude Code 支持四种钩子类型:
Command 钩子
默认钩子类型,执行 Shell 命令,通过 JSON stdin/stdout 和退出码通信。
HTTP 钩子
v2.1.63 起可用。
远程 Webhook 端点,接收与 command 钩子相同的 JSON 输入。通过 POST 发送 JSON 并接收 JSON 响应。
json
{
"hooks": {
"PostToolUse": [{
"type": "http",
"url": "https://my-webhook.example.com/hook",
"matcher": "Write"
}]
}
}Prompt 钩子
LLM 评估的提示词钩子,主要用于 Stop 和 SubagentStop 事件的智能任务完成检查。
json
{
"type": "prompt",
"prompt": "评估 Claude 是否完成了所有请求的任务。",
"timeout": 30
}Agent 钩子
基于子代理的验证钩子,生成专用代理来评估条件或执行复杂检查。与 prompt 钩子不同,agent 钩子可以使用工具并进行多步推理。
json
{
"type": "agent",
"prompt": "验证代码变更是否符合架构规范,检查相关设计文档并对比。",
"timeout": 120
}钩子事件
Claude Code 支持 25 个钩子事件:
| 事件 | 触发时机 | 是否可阻断 | 常见用途 |
|---|---|---|---|
| SessionStart | 会话开始/恢复/清空/压缩 | 否 | 环境初始化 |
| InstructionsLoaded | 加载 CLAUDE.md 或规则文件后 | 否 | 过滤/修改指令 |
| UserPromptSubmit | 用户提交提示词 | 是 | 校验输入 |
| PreToolUse | 工具执行前 | 是(allow/deny/ask) | 校验、修改输入 |
| PermissionRequest | 显示权限对话框 | 是 | 自动批准/拒绝 |
| PostToolUse | 工具执行成功后 | 否 | 添加上下文、反馈 |
| PostToolUseFailure | 工具执行失败 | 否 | 错误处理、日志 |
| Notification | 发送通知时 | 否 | 自定义通知 |
| SubagentStart | 子代理启动 | 否 | 子代理初始化 |
| SubagentStop | 子代理完成 | 是 | 子代理验证 |
| Stop | Claude 完成响应 | 是 | 任务完成检查 |
| StopFailure | API 错误终止轮次 | 否 | 错误恢复、日志 |
| TeammateIdle | 代理团队成员空闲 | 是 | 成员协调 |
| TaskCompleted | 任务标记完成 | 是 | 任务后置动作 |
| TaskCreated | 通过 TaskCreate 创建任务 | 否 | 任务跟踪、日志 |
| ConfigChange | 配置文件变更 | 是 | 响应配置更新 |
| CwdChanged | 工作目录变更 | 否 | 目录特定初始化 |
| FileChanged | 被监视文件变更 | 否 | 文件监控、重建 |
| PreCompact | 上下文压缩前 | 否 | 压缩前动作 |
| PostCompact | 压缩完成后 | 否 | 压缩后动作 |
| WorktreeCreate | 创建 worktree | 是 | Worktree 初始化 |
| WorktreeRemove | 删除 worktree | 否 | Worktree 清理 |
| Elicitation | MCP 服务器请求用户输入 | 是 | 输入校验 |
| ElicitationResult | 用户响应 elicitation | 是 | 响应处理 |
| SessionEnd | 会话终止 | 否 | 清理、最终日志 |
钩子输入输出
JSON 输入(via stdin)
所有钩子通过 stdin 接收 JSON 输入:
json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.js",
"content": "..."
}
}退出码
| 退出码 | 含义 | 行为 |
|---|---|---|
| 0 | 成功 | 继续,解析 JSON stdout |
| 2 | 阻断错误 | 阻断操作,stderr 作为错误显示 |
| 其他 | 非阻断错误 | 继续,详细模式下显示 stderr |
JSON 输出(stdout,退出码 0)
json
{
"continue": true,
"suppressOutput": false,
"systemMessage": "可选警告消息",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "文件在允许的目录中",
"updatedInput": {
"file_path": "/modified/path.js"
}
}
}环境变量
| 变量 | 可用范围 | 说明 |
|---|---|---|
CLAUDE_PROJECT_DIR | 所有钩子 | 项目根目录绝对路径 |
CLAUDE_ENV_FILE | SessionStart、CwdChanged、FileChanged | 持久化环境变量的文件路径 |
CLAUDE_CODE_REMOTE | 所有钩子 | 在远程环境运行时为 "true" |
${CLAUDE_PLUGIN_ROOT} | 插件钩子 | 插件目录路径 |
${CLAUDE_PLUGIN_DATA} | 插件钩子 | 插件数据目录路径 |
示例
示例一:Bash 命令校验器(PreToolUse)
文件: .claude/hooks/validate-bash.py
python
#!/usr/bin/env python3
import json
import sys
import re
BLOCKED_PATTERNS = [
(r"\brm\s+-rf\s+/", "阻断危险命令 rm -rf /"),
(r"\bsudo\s+rm", "阻断 sudo rm 命令"),
]
def main():
input_data = json.load(sys.stdin)
if input_data.get("tool_name") != "Bash":
sys.exit(0)
command = input_data.get("tool_input", {}).get("command", "")
for pattern, message in BLOCKED_PATTERNS:
if re.search(pattern, command):
print(message, file=sys.stderr)
sys.exit(2) # 退出码 2 = 阻断错误
sys.exit(0)
if __name__ == "__main__":
main()配置:
json
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\""
}]
}]
}
}示例二:安全扫描器(PostToolUse)
python
#!/usr/bin/env python3
import json, sys, re
SECRET_PATTERNS = [
(r"password\s*=\s*['\"][^'\"]+['\"]", "潜在硬编码密码"),
(r"api[_-]?key\s*=\s*['\"][^'\"]+['\"]", "潜在硬编码 API Key"),
]
def main():
input_data = json.load(sys.stdin)
if input_data.get("tool_name") not in ["Write", "Edit"]:
sys.exit(0)
content = input_data.get("tool_input", {}).get("content", "")
file_path = input_data.get("tool_input", {}).get("file_path", "")
warnings = [msg for pat, msg in SECRET_PATTERNS if re.search(pat, content, re.IGNORECASE)]
if warnings:
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": f"安全警告 {file_path}: " + "; ".join(warnings)
}
}))
sys.exit(0)示例三:自动格式化代码(PostToolUse)
bash
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_name', ''))")
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_input', {}).get('file_path', ''))")
[ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ] && exit 0
case "$FILE_PATH" in
*.js|*.jsx|*.ts|*.tsx|*.json)
command -v prettier &>/dev/null && prettier --write "$FILE_PATH" 2>/dev/null ;;
*.py)
command -v black &>/dev/null && black "$FILE_PATH" 2>/dev/null ;;
*.go)
command -v gofmt &>/dev/null && gofmt -w "$FILE_PATH" 2>/dev/null ;;
esac
exit 0示例四:智能 Stop 钩子(基于 Prompt)
json
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "prompt",
"prompt": "检查 Claude 是否完成了所有请求的任务。检查:1)所有文件是否已创建/修改?2)是否有未解决的错误?如未完成,说明缺少什么。",
"timeout": 30
}]
}]
}
}完整配置示例
json
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\"", "timeout": 10}]
}],
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [
{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh\"", "timeout": 30},
{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py\"", "timeout": 10}
]
}],
"UserPromptSubmit": [{
"hooks": [{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py\""}]
}],
"Stop": [{
"hooks": [{"type": "prompt", "prompt": "在停止前验证所有任务已完成。", "timeout": 30}]
}]
}
}安全注意事项
使用风险自负:钩子执行任意 Shell 命令,请确保:
- 测试钩子在安全环境中运行
- 校验和清理所有输入
- 为 Shell 变量加引号:
"$VAR" - 使用
$CLAUDE_PROJECT_DIR的绝对路径 - HTTP 钩子需显式指定
allowedEnvVars
安装步骤
bash
# 1. 创建钩子目录
mkdir -p ~/.claude/hooks
# 2. 复制示例钩子
cp 06-hooks/*.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/*.sh
# 3. 在设置文件中配置
# 编辑 ~/.claude/settings.json 或 .claude/settings.json调试
bash
# 开启调试模式
claude --debug
# 独立测试钩子
echo '{"tool_name": "Bash", "tool_input": {"command": "ls"}}' | python3 .claude/hooks/validate-bash.py
echo $?