Windows 10 · Claude Code CLI v2.x · DeepSeek V4 Pro / Anthropic API · 2026-05-01
一、这篇教程解决什么问题
一句话定位:Claude Code 能自主执行工具调用,但你无法保证它不会误删生产文件、编辑 .env 秘密文件、或者在没跑测试的情况下就提交代码。Hooks 就是你的"安全看门狗"——在工具执行前拦截、执行后审计、会话开始时加载上下文、会话结束时清理资源,全程自动化守护。
阅读前提:
- 已完成 Claude Code CLI 安装,
claude --version正常输出版本号 - 已完成
/init项目初始化,项目根目录存在CLAUDE.md - 了解
settings.json的三层配置机制(全局 → 项目共享 → 项目本地)
读完能得到什么:
- 理解 Hooks 的事件驱动机制(6 大核心事件 + 30+ 生命周期事件)
- 掌握 5 种 Hook 处理器类型(command / http / mcp_tool / prompt / agent)
- 10 个生产级实战 Hook 示例(拦截危险命令、格式化代码、加载 Git 上下文、强制测试等)
- 现成的优质-hooks插件推荐-不用自己写,全功能覆盖
- Windows 环境下 4 个特有坑位的五段式 Debug
- 一张速查卡,包含完整事件表、匹配器语法、退出码语义
二、Hooks 是什么
2.1 一句话解释
Hook 是你注册在 Claude Code 生命周期特定节点上的自定义脚本。当事件触发时,Claude Code 通过 stdin 向你的脚本发送 JSON 上下文,你的脚本通过退出码和 stdout 告诉 Claude Code"放行"或"拦截"。
2.2 Hooks 与 Skills / MCP 的区别
| 维度 | Hooks | Skills | MCP |
|---|---|---|---|
| 本质 | 事件驱动的自动化脚本 | 知识注入(CLAUDE.md 风格) | 外部工具连接协议 |
| 触发方式 | 生命周期事件自动触发 | Claude 按需调用 / 用户手动 /skill | Claude 主动调用 MCP 工具 |
| 能否拦截/阻止操作 | ✅ 可以(exit 2 或 JSON decision) | ❌ 不能 | ❌ 不能 |
| 能否修改工具输入 | ✅ 可以(updatedInput) | ❌ 不能 | ❌ 不能 |
| 典型用途 | 安全守护、代码格式化、审计日志 | 编码规范、设计模式、最佳实践 | 查询数据库、操作 GitHub、抓网页 |
2.3 事件生命周期全景
会话启动
├── SessionStart ← 加载 Git 状态、环境变量
├── Setup ← CI 一次性依赖安装
└── InstructionsLoaded ← CLAUDE.md 被加载时
用户输入
├── UserPromptSubmit ← 拦截/验证用户提示
└── UserPromptExpansion ← 拦截斜杠命令展开
Claude 执行工具(循环)
├── PreToolUse ← 工具执行前:拦截/审批/修改输入
├── PermissionRequest ← 权限弹窗出现时
├── PermissionDenied ← 自动模式拒绝时
├── PostToolUse ← 工具成功后:格式化/审计
├── PostToolUseFailure ← 工具失败后
└── PostToolBatch ← 并行工具批次完成后
其他事件
├── Notification ← 通知发送时
├── SubagentStart/Stop ← 子代理生命周期
├── TaskCreated/Completed ← 任务创建/完成
├── ConfigChange ← 配置文件变更
├── CwdChanged ← 工作目录切换
├── FileChanged ← 监视文件变更
├── WorktreeCreate/Remove ← Git Worktree 生命周期
├── PreCompact/PostCompact← 上下文压缩前后
└── Elicitation/Result ← MCP 服务器请求用户输入
会话结束
├── Stop / StopFailure ← Claude 停止响应时
└── SessionEnd ← 会话终止
三、快速上手:5 分钟写出第一个 Hook
3.1 创建项目级 hooks 目录
mkdir .claude\hooks
3.2 编写拦截脚本
创建文件 .claude/hooks/block-rm-rf.sh:
#!/bin/bash
# 读取 stdin 中的 JSON,提取 Bash 命令
COMMAND=$(jq -r '.tool_input.command' < /dev/stdin)
if echo "$COMMAND" | grep -q 'rm -rf\|rm -r -f'; then
echo "Blocked: rm -rf is not allowed by project hook" >&2
exit 2 # 阻止工具执行
fi
exit 0 # 放行
3.3 在 settings.json 中注册 Hook
编辑项目级 .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-rm-rf.sh\""
}
]
}
]
}
}
matcher字段:精确匹配工具名。Bash只匹配 Bash 工具,Edit|Write匹配任一,*或省略匹配所有工具。
3.4 验证
启动 Claude Code,尝试让它执行 rm -rf:
帮我删除 /tmp/build 目录:rm -rf /tmp/build
预期结果:Claude 尝试执行 rm -rf /tmp/build 时被 Hook 拦截,终端显示:
Blocked: rm -rf is not allowed by project hook
Claude 看到这个错误信息后会尝试其他安全的删除方式。
四、配置详解:三层嵌套结构
4.1 settings.json 中的 hooks 字段
Hook 配置有三层嵌套:事件 → 匹配器组 → 处理器列表。
{
"hooks": {
"事件名": [
{
"matcher": "匹配器",
"hooks": [
{
"type": "command",
"command": "脚本路径或命令",
"timeout": 600
}
]
}
]
}
}
| 层级 | 字段 | 说明 |
|---|---|---|
| 第 1 层 | hooks.<事件名> | 生命周期事件,如 PreToolUse、PostToolUse、Stop |
| 第 2 层 | matcher | 过滤器:当事件触发时,只有匹配的工具名才激活此组 |
| 第 3 层 | hooks[] | 处理器列表:匹配成功后执行的脚本/HTTP/MCP/提示 |
4.2 匹配器语法(matcher)
| 值 | 含义 | 示例 | |
|---|---|---|---|
"*" 或 "" 或省略 | 匹配所有 | 每次事件触发都执行 | |
| 纯字母/数字/下划线/竖线 | 精确匹配或 ` | ` 分隔列表 | Bash、Edit|Write |
含其他字符(.、*、^ 等) | JavaScript 正则表达式 | ^Notebook、mcp__memory__.* |
MCP 工具命名格式:
mcp__<服务器名>__<工具名>。匹配某个服务器所有工具用mcp__memory__.*(必须加.*,否则精确匹配不生效)。
4.3 处理器类型(5 种)
| 类型 | 说明 | 关键字段 |
|---|---|---|
command | 执行 Shell 命令(默认) | command、shell(bash/powershell)、async、timeout |
http | 发送 HTTP POST 请求 | url、headers、allowedEnvVars、timeout |
mcp_tool | 调用已连接的 MCP 服务器工具 | server、tool、input |
prompt | 发送提示词给 Claude 模型做单轮评估 | prompt、model、timeout(默认 30s) |
agent | 启动子代理验证条件(实验性) | prompt、model、timeout(默认 60s) |
4.4 if 条件字段(精细过滤)
if 使用权限规则语法,在处理器级别进一步过滤:
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-rm.sh\""
}
]
}
matcher 和 if 的区别:
matcher:事件级过滤,匹配工具名if:处理器级过滤,匹配工具名 + 参数(如Bash(git push *)、Edit(*.ts))- 两者都匹配时,Hook 才执行
注意:
if仅在工具事件(PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied)上生效。其他事件设置if会导致该 Hook 永远不执行。
4.5 配置文件位置与优先级
| 位置 | 作用域 | 可 Git 提交 |
|---|---|---|
~/.claude/settings.json | 所有项目 | ❌ 仅本机 |
.claude/settings.json | 当前项目 | ✅ 团队共享 |
.claude/settings.local.json | 当前项目 | ❌ gitignore |
插件 hooks/hooks.json | 插件启用时 | ✅ 随插件分发 |
| Skill/Agent frontmatter | 组件活跃时 | ✅ 随组件分发 |
五、退出码与 JSON 输出:Hook 的"决策语言"
5.1 退出码语义
| 退出码 | 含义 | PreToolUse 效果 | PostToolUse 效果 | Stop 效果 |
|---|---|---|---|---|
0 | 成功/放行 | 工具继续执行 | 无额外效果 | Claude 停止 |
2 | 阻止/拦截 | 阻止工具执行 | stderr 展示给 Claude | 阻止停止,继续对话 |
| 其他 | 非阻塞错误 | 工具继续执行 | stderr 展示给 Claude | Claude 停止 |
5.2 退出码 2 在各事件上的行为
| 事件 | 能阻止? | 退出码 2 的效果 |
|---|---|---|
PreToolUse | ✅ | 阻止工具调用 |
UserPromptSubmit | ✅ | 阻止提示处理并清除提示 |
Stop | ✅ | 阻止 Claude 停止,继续对话 |
PreCompact | ✅ | 阻止上下文压缩 |
PermissionRequest | ✅ | 拒绝权限请求 |
SubagentStop | ✅ | 阻止子代理停止 |
PostToolUse | ❌ | stderr 展示给 Claude(工具已执行) |
PostToolUseFailure | ❌ | stderr 展示给 Claude(工具已失败) |
SessionStart | ❌ | stderr 仅展示给用户 |
SessionEnd | ❌ | stderr 仅展示给用户 |
Notification | ❌ | stderr 仅展示给用户 |
5.3 JSON 输出(精细控制)
退出码只区分"放行"和"阻止"两档。JSON 输出让你有更精细的控制权——允许、拒绝、询问用户、注入上下文、修改工具输入。
Hook 脚本退出码为 0 时,stdout 中的 JSON 会被解析。退出码非 0 时 JSON 被忽略。
通用字段
{
"continue": true,
"stopReason": "停止原因(continue=false 时展示)",
"suppressOutput": false,
"systemMessage": "展示给用户的警告消息"
}
注入上下文(additionalContext)
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "当前分支: feat/auth. 注意: config/prod.yml 是只读的。"
}
}
additionalContext 会被包装为系统提醒注入 Claude 的上下文窗口。适合传递动态环境信息(当前分支、部署目标、活跃 feature flag 等)。
PreToolUse 精细决策(permissionDecision)
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "数据库写操作被 Hook 禁止"
}
}
| permissionDecision | 效果 |
|---|---|
"allow" | 跳过权限提示,直接执行 |
"deny" | 阻止工具调用 |
"ask" | 提示用户确认 |
"defer" | 优雅退出,工具稍后恢复(仅 -p 非交互模式) |
优先级(多个 Hook 返回不同决策时):deny > defer > ask > allow
修改工具输入(updatedInput)
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": {
"command": "npm run lint && npm test"
}
}
}
updatedInput 替换整个 tool_input 对象,所以必须包含未修改的字段。
停止 Claude(全局控制)
{
"continue": false,
"stopReason": "构建失败,请先修复错误再继续"
}
此字段跨所有事件生效。
六、实战 Hook 示例
6.1 PreToolUse:拦截危险命令
场景:阻止 rm -rf、dd if=、mkfs、format 等破坏性命令。
脚本 .claude/hooks/block-dangerous.sh:
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command' < /dev/stdin)
if echo "$COMMAND" | grep -qE 'rm -rf|rm -r -f|dd if=|mkfs|format '; then
echo "Destructive command blocked: $COMMAND" >&2
exit 2
fi
exit 0
配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous.sh\""
}
]
}
]
}
}
if字段的作用:"if": "Bash(rm *)"使得只有 Bash 命令以rm开头时才运行脚本,避免每次 Bash 调用都启动一个进程。
6.2 PreToolUse:禁止编辑敏感文件
场景:阻止 Claude 编辑 .env、credentials.json、secrets.yaml 等秘密文件。
脚本 .claude/hooks/protect-secrets.sh:
#!/bin/bash
FILE_PATH=$(jq -r '.tool_input.file_path // ""' < /dev/stdin)
if echo "$FILE_PATH" | grep -qE '\.env$|credentials|secrets|\.pem$|\.key$'; then
echo "Sensitive file blocked: $FILE_PATH" >&2
exit 2
fi
exit 0
配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect-secrets.sh\""
}
]
}
]
}
}
6.3 PostToolUse:自动格式化代码
场景:Claude 写入或编辑 Python 文件后,自动运行 ruff format。
脚本 .claude/hooks/auto-format.sh:
#!/bin/bash
TOOL_NAME=$(jq -r '.tool_name' < /dev/stdin)
FILE_PATH=$(jq -r '.tool_input.file_path // ""' < /dev/stdin)
# 只处理 Python 文件
if [[ "$FILE_PATH" == *.py ]]; then
ruff format "$FILE_PATH" 2>/dev/null || true
ruff check --fix "$FILE_PATH" 2>/dev/null || true
fi
exit 0
配置:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/auto-format.sh\"",
"timeout": 30
}
]
}
]
}
}
PostToolUse 不能阻止操作(工具已执行),但可以做审计和后处理。如果格式化失败,stderr 会被展示给 Claude,Claude 可能会自行修复。
6.4 SessionStart:加载 Git 状态到上下文
场景:每次会话开始时,自动把当前分支名、未提交变更文件列表注入 Claude 的上下文。
脚本 .claude/hooks/load-git-status.sh:
#!/bin/bash
# 检查是否在 Git 仓库中
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
exit 0
fi
BRANCH=$(git branch --show-current 2>/dev/null)
CHANGED=$(git diff --name-only 2>/dev/null)
STAGED=$(git diff --cached --name-only 2>/dev/null)
UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null)
OUTPUT="Current branch: $BRANCH"
if [ -n "$CHANGED" ]; then
OUTPUT="$OUTPUT\nUnstaged changes: $CHANGED"
fi
if [ -n "$STAGED" ]; then
OUTPUT="$OUTPUT\nStaged files: $STAGED"
fi
if [ -n "$UNTRACKED" ]; then
OUTPUT="$OUTPUT\nUntracked files: $UNTRACKED"
fi
echo -e "$OUTPUT"
exit 0
配置:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/load-git-status.sh\"",
"timeout": 10
}
]
}
]
}
}
SessionStart 的 stdout 直接注入 Claude 上下文。无需构建 JSON,纯文本即可。
6.5 SessionStart:持久化环境变量
场景:会话开始时设置 NODE_ENV、PATH 等环境变量,后续所有 Bash 命令都生效。
脚本 .claude/hooks/setup-env.sh:
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi
exit 0
配置:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/setup-env.sh\""
}
]
}
]
}
}
$CLAUDE_ENV_FILE是 Claude Code 提供的特殊路径。写入该文件的export语句会在后续所有 Bash 工具中自动生效。仅在SessionStart、Setup、CwdChanged、FileChanged事件中可用。
6.6 Stop:强制运行测试才能停止
场景:Claude 想停止对话前,检查本次会话是否运行过测试。没跑过就阻止停止。
脚本 .claude/hooks/require-tests.sh:
#!/bin/bash
# 读取会话 transcript 检查是否执行过测试命令
TRANSCRIPT=$(jq -r '.transcript_path' < /dev/stdin 2>/dev/null)
if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
if grep -qE '"(npm test|pytest|cargo test|go test)"' "$TRANSCRIPT" 2>/dev/null; then
exit 0 # 已运行过测试,放行
fi
fi
echo "Tests have not been run in this session. Please run tests before stopping." >&2
exit 2 # 未运行测试,阻止停止
配置:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/require-tests.sh\"",
"timeout": 10
}
]
}
]
}
}
Stop 事件退出码 2 的效果:阻止 Claude 停止,Claude 看到 stderr 中的错误信息后会继续对话(自动运行测试)。
6.7 UserPromptSubmit:自动为提示注入上下文
场景:用户提交提示时,自动附加当前 Git 分支信息。
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "echo \"Git branch: $(git branch --show-current 2>/dev/null)\""
}
]
}
]
}
}
UserPromptSubmit 的 stdout 直接注入 Claude 上下文。此事件不支持 matcher(每次都触发)。
6.8 PreToolUse:JSON 精细控制——自动审批 git status
场景:Claude 执行 git status、git log、git diff 等只读 Git 命令时,自动跳过权限提示。
脚本 .claude/hooks/auto-approve-git-readonly.sh:
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command' < /dev/stdin)
# 匹配只读 git 命令
if echo "$COMMAND" | grep -qE '^git (status|log|diff|show|branch|remote)'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
permissionDecisionReason: "Read-only git command auto-approved"
}
}'
exit 0
fi
exit 0 # 其他命令走正常流程
配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git *)",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/auto-approve-git-readonly.sh\""
}
]
}
]
}
}
6.9 异步 Hook:文件变更后后台运行测试
场景:Claude 每次编辑文件后,在后台自动运行测试,不阻塞主流程。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "cd \"$CLAUDE_PROJECT_DIR\" && npm test 2>&1 | tail -20",
"async": true,
"timeout": 120
}
]
}
]
}
}
async: true:Hook 在后台运行,不阻塞 Claude 的下一次工具调用。asyncRewake: true:后台运行,且退出码为 2 时唤醒 Claude(stderr/stdout 作为系统提醒注入)。
6.10 Prompt Hook:用 AI 判断是否放行
场景:用轻量模型评估 Bash 命令是否安全。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate this Bash command for safety. Is it destructive or could it cause data loss? Command: $ARGUMENTS. Reply with JSON: {\"decision\": \"block\", \"reason\": \"...\"} if unsafe, or {} if safe.",
"timeout": 15
}
]
}
]
}
}
$ARGUMENTS:占位符,会被替换为 Hook 的完整 JSON 输入。Prompt hook 默认使用快速模型(30 秒超时)。
七、Windows 环境专题
7.1 使用 PowerShell 作为 Hook Shell
在 Windows 上,可以将 shell 设置为 powershell:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"shell": "powershell",
"command": "$input = $input | ConvertFrom-Json; if ($input.tool_input.command -match 'rm -rf') { Write-Error 'Blocked'; exit 2 }"
}
]
}
]
}
}
shell: "powershell":Claude Code 直接通过 PowerShell 执行命令,无需cmd /c包装。此字段仅在 Windows 上有效。
7.2 使用 Git Bash 执行 .sh 脚本
如果你安装了 Git for Windows(自带 Git Bash),可以直接运行 .sh 脚本:
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-rm-rf.sh\""
}
前提:Git Bash(
bash.exe)在系统 PATH 中。安装 Git for Windows 时勾选"Add to PATH"即可。
7.3 用 Python 替代 Bash 脚本
对于复杂逻辑,Python 比 Bash 更可靠(Windows 上 Bash 行为不一致):
{
"type": "command",
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous.py\""
}
Python 脚本示例 .claude/hooks/block-dangerous.py:
#!/usr/bin/env python3
import json, sys
input_data = json.load(sys.stdin)
command = input_data.get("tool_input", {}).get("command", "")
if any(kw in command for kw in ["rm -rf", "dd if=", "mkfs"]):
print("Destructive command blocked", file=sys.stderr)
sys.exit(2)
sys.exit(0)
7.4 路径中的空格和特殊字符
Hook 配置中引用项目目录时,始终用引号包裹 $CLAUDE_PROJECT_DIR:
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.py\""
Windows 用户名可能包含空格(如
C:\Users\John Doe\),不加引号会导致路径断裂。
八、高级特性速览
8.1 HTTP Hook
将事件数据 POST 到远程服务:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/audit",
"timeout": 10,
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
HTTP Hook 的非 2xx 响应、连接失败、超时都是非阻塞错误,不会阻止 Claude 执行。要阻止操作需返回 2xx + JSON body 中包含
decision: "block"。
8.2 MCP Tool Hook
调用已连接的 MCP 服务器工具:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "my_security_server",
"tool": "scan_file",
"input": {
"file_path": "${tool_input.file_path}"
}
}
]
}
]
}
}
${tool_input.file_path}是变量替换语法,从 Hook 输入 JSON 中提取值。
8.3 环境变量
Hook 脚本可使用的环境变量:
| 变量 | 说明 | 可用事件 |
|---|---|---|
$CLAUDE_PROJECT_DIR | 项目根目录 | 所有 |
${CLAUDE_PLUGIN_ROOT} | 插件安装目录 | 所有 |
${CLAUDE_PLUGIN_DATA} | 插件持久数据目录 | 所有 |
$CLAUDE_ENV_FILE | 环境变量持久化文件 | SessionStart、Setup、CwdChanged、FileChanged |
$CLAUDE_CODE_REMOTE | 远程环境标记("true" 或未设置) | 所有 |
8.4 /hooks 菜单
在 Claude Code 交互会话中输入:
/hooks
可查看所有已注册的 Hook,按事件分类展示,包括来源(User / Project / Local / Plugin)和完整配置详情。
8.5 临时禁用所有 Hook
在 settings.json 中设置:
{
"disableAllHooks": true
}
此设置无法禁用管理员通过 managed policy 下发的 Hook。要禁用 managed hooks,必须在 managed-settings.json 中设置。
八½、现成的优质 Hooks 插件——不用自己写
前面的实战 Hook 都是手写的。好消息是,社区已经有大量成熟的 Hooks 插件可以直接安装使用。以下按场景分类推荐。
8.6 官方插件(Anthropic 维护)
| 插件 | 事件 | 功能 | 安装方式 |
|---|---|---|---|
| security-guidance | PreToolUse | 扫描 9 类安全漏洞(XSS、注入、eval、pickle 反序列化等),自动拦截 | /plugin install security-guidance@anthropic-plugins |
| hookify | PreToolUse / Stop / UserPromptSubmit | 用 Markdown + YAML 写规则,不需要编辑 JSON。支持 /hookify 命令交互式创建规则 | /plugin install hookify@anthropic-plugins |
| ralph-wiggum | Stop | 拦截 Claude 的停止尝试,自动重放提示词形成迭代循环,直到任务真正完成 | /plugin install ralph-wiggum@anthropic-plugins |
| explanatory-output-style | SessionStart | 会话启动时注入指令,让 Claude 在实现时解释设计决策 | /plugin install explanatory-output-style@anthropic-plugins |
| learning-output-style | SessionStart | 在关键决策点让 Claude 要求你手写 5-10 行代码,而非全部自动生成 | /plugin install learning-output-style@anthropic-plugins |
security-guidance 是最推荐优先安装的官方插件。它覆盖了 OWASP Top 10 中最常见的前端漏洞模式,零配置开箱即用。
8.7 社区精选(按星标排序)
| 插件/仓库 | ⭐ Stars | 事件 | 功能 | 安装方式 |
|---|---|---|---|---|
| tdd-guard | ~2,050 | PreToolUse + Stop | TDD 铁律守护:没有失败的测试就禁止写实现代码。支持 Jest / Vitest / pytest / Go / Rust | /plugin marketplace add nizos/tdd-guard → /plugin install tdd-guard@tdd-guard → /tdd-guard:setup |
| claude-code-safety-net | ~1,295 | PreToolUse | 语义级命令分析(非正则匹配),拦截 git push --force main、rm -rf / 等破坏性命令。支持 5 层 shell 包装检测 | /plugin marketplace add kenryu42/cc-marketplace → /plugin install safety-net@cc-marketplace |
| claude-code-prompt-improver | ~1,407 | UserPromptSubmit | 拦截模糊提示词,自动追问 1-6 个澄清问题。清晰提示零开销,模糊提示提升质量,实测减少 31% token 消耗 | /plugin marketplace add severity1/severity1-marketplace → /plugin install prompt-improver@severity1-marketplace |
| claude-hook-cookbook | — | 多种 | 9 个零依赖独立 shell 脚本,覆盖:自动格式化、lint、git 安全、会话日志、上下文重载、停止前测试、桌面通知、对话备份、API Key 防泄漏 | git clone 到 .claude/hooks/ 目录 |
| claude-code-infrastructure-showcase | ~9,605 | 多种 | 6 个月生产验证的 Hook 模式集合:TypeScript 类型检查门禁、构建验证、错误处理提醒 | 参考仓库中的 hooks 配置复制到项目 |
| pilot-shell | ~1,676 | 多种 | 完整工程平台:lint / format / type-check / test 四重质量门禁 + token 优化(实测减少 60-90% 开销) | curl -fsSL https://raw.githubusercontent.com/maxritter/pilot-shell/main/install.sh | bash |
8.8 按场景速查:该装哪个
| 你的需求 | 推荐插件 | 一句话理由 |
|---|---|---|
| 防止 Claude 写出安全漏洞 | security-guidance(官方) | 9 类漏洞模式自动扫描,零配置 |
| 用自然语言写 Hook 规则 | hookify(官方) | Markdown 写规则,不用碰 JSON |
| 强制 TDD 开发 | tdd-guard | 没有失败测试就禁止写代码 |
| 拦截危险 Git / 文件操作 | claude-code-safety-net | 语义分析,不只是正则匹配 |
| 提升提示词质量 | claude-code-prompt-improver | 模糊提问自动追问,省 31% token |
| 停止前自动跑测试 | claude-hook-cookbook 的 test-on-stop.sh | 一个脚本搞定,零依赖 |
| 编辑后自动格式化 | claude-hook-cookbook 的 auto-format.sh | 支持 prettier / black / gofmt / rustfmt |
| 会话结束备份对话 | claude-hook-cookbook 的 transcript-backup.sh | SessionEnd 自动触发 |
| Claude 停不下来(迭代循环) | ralph-wiggum(官方) | /ralph-loop 启动自动迭代 |
| 全面工程化(团队) | pilot-shell | 四重门禁 + token 优化一步到位 |
8.9 快速安装指南
方式一:Plugin 市场安装(推荐)
# 在 Claude Code 交互会话中执行
/plugin marketplace add nizos/tdd-guard
/plugin install tdd-guard@tdd-guard
/tdd-guard:setup
方式二:git clone 安装
# 以 claude-hook-cookbook 为例
git clone https://github.com/echo-lumen/claude-hook-cookbook.git C:\Workspace\你的项目\.claude\hooks\cookbook
# 然后在 settings.json 中引用脚本路径
方式三:手动复制单个脚本
从仓库中挑选需要的单个脚本文件,复制到 .claude/hooks/ 目录,然后在 settings.json 中注册。
国内用户注意:
git clone可能超时。解决方案同 MCP 章节——配置 GitHub 镜像或代理。Plugin 市场安装走 npm 通道,已配置 npmmirror 镜像的用户可直接使用。
九、Debug #1 — Hook 脚本不执行(无任何效果)
报错日志
在 settings.json 中配置了 Hook,但 Claude 执行匹配的工具时没有任何拦截或输出。
`/hooks` 菜单中可以看到 Hook 已注册。
根因
Hook 脚本不执行最常见的三个原因:
- 脚本文件没有执行权限(Linux/macOS):
chmod +x未执行 - matcher 或 if 过滤条件不匹配:matcher 是大小写敏感的,
bash≠Bash - 脚本路径中的
$CLAUDE_PROJECT_DIR未被正确解析:JSON 中未加引号,路径含空格时断裂
一览对比表
| 对比维度 | 正常情况 | 异常情况 |
|---|---|---|
| matcher 值 | Bash(首字母大写) | bash(小写,不匹配) |
| 脚本路径 | "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/script.sh\"" | bash $CLAUDE_PROJECT_DIR/.claude/hooks/script.sh(无引号) |
| 脚本权限 | chmod +x script.sh(Linux) | 默认无执行权限 |
| 配置文件位置 | 正确层级的 settings.json | 配置写在了不生效的层级 |
代码修复
检查一:确认 Hook 已注册
/hooks
在菜单中查看目标事件下是否有你的 Hook。如果没有,检查 settings.json 的 JSON 语法是否正确。
检查二:matcher 大小写
// 错误
"matcher": "bash"
// 正确
"matcher": "Bash"
检查三:启用调试日志
claude --debug
调试模式下,Hook 的执行过程(输入 JSON、退出码、stdout、stderr)会完整输出到日志。
验证
claude --debug
# 让 Claude 执行一个匹配的操作,查看调试输出中是否有 Hook 执行记录
十、Debug #2 — Hook JSON 解析失败
报错日志
Hook error: JSON validation failed
"hookName": "my-pre-hook",
"error": "Unexpected token '<' at position 0"
根因
Hook 脚本的 stdout 中混入了非 JSON 文本,Claude Code 解析失败。
常见原因:
- Shell profile 中有 echo 语句:
.bashrc或.bash_profile中的echo在 Hook 脚本执行时也会输出到 stdout - Python 的 print 调试语句:Hook 脚本中残留的
print("debug")混入 stdout - jq 的格式化输出:
jq默认带颜色高亮,在某些终端输出 ANSI 转义字符
一览对比表
| 对比维度 | 正常情况 | 异常情况 |
|---|---|---|
| stdout 输出 | 仅包含合法 JSON 或为空 | 包含 echo/print 的调试文本 |
| Shell profile | 无输出语句 | .bashrc 中有 echo "Welcome" |
| jq 调用 | jq -n '{...}' | jq '{...}' 可能带 ANSI 颜色 |
代码修复
方案一:将调试输出重定向到 stderr
# 错误:print 输出到 stdout
echo "Debug: checking command"
# 正确:调试信息输出到 stderr
echo "Debug: checking command" >&2
方案二:Python 中使用 stderr
# 错误
print("Debug info")
# 正确
print("Debug info", file=sys.stderr)
方案三:确保 Shell profile 不干扰
在 Hook 脚本开头添加:
#!/bin/bash
# 确保 stdout 不被 profile 污染
exec >/dev/null 2>&1 # 如果不需要输出
# 或者只在需要输出 JSON 时恢复 stdout
验证
# 直接运行脚本,检查 stdout 是否只有 JSON
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | python .claude/hooks/my-hook.py
# 应只输出 JSON 或空行,不包含调试文本
十一、Debug #3 — Windows 上 Bash 脚本执行失败
报错日志
Hook error: Command failed with exit code 127
"hookName": "block-rm",
"stderr": "bash: /c/Users/.../.claude/hooks/block-rm.sh: No such file or directory"
或:
Hook error: Command failed
"stderr": "'jq' is not recognized as an internal or external command"
根因
Windows 上运行 Bash 脚本有两个常见坑:
bash命令不在 PATH 中:未安装 Git for Windows,或安装时未勾选"Add to PATH"jq未安装:脚本中使用jq解析 JSON,但 Windows 默认不自带 jq- 路径分隔符:Windows 使用
\,而 Bash 脚本期望/
一览对比表
| 对比维度 | 正常情况 | 异常情况 |
|---|---|---|
| bash 命令 | Git Bash 已安装且在 PATH 中 | bash 命令不可用 |
| jq 工具 | 已安装且在 PATH 中 | Windows 默认无 jq |
| 路径分隔符 | $CLAUDE_PROJECT_DIR 展开为正斜杠或被引号包裹 | 反斜杠导致 Bash 解析失败 |
代码修复
方案一:用 Python 替代 Bash + jq
#!/usr/bin/env python3
"""替代 Bash+jq 的跨平台 Hook 脚本"""
import json, sys
data = json.load(sys.stdin)
command = data.get("tool_input", {}).get("command", "")
if "rm -rf" in command:
print("Blocked", file=sys.stderr)
sys.exit(2)
方案二:安装 jq for Windows
winget install jqlang.jq
# 或
scoop install jq
方案三:使用 PowerShell 作为 Hook Shell
{
"type": "command",
"shell": "powershell",
"command": "$data = [Console]::In.ReadToEnd() | ConvertFrom-Json; if ($data.tool_input.command -match 'rm -rf') { exit 2 }"
}
验证
# 确认 bash 可用
bash --version
# 确认 jq 可用
jq --version
# 测试 Hook 脚本
echo '{"tool_input":{"command":"rm -rf /tmp"}}' | python .claude/hooks/block-dangerous.py
echo %ERRORLEVEL% # 应输出 2
十二、Debug #4 — Hook 超时导致 Claude 卡顿
报错日志
Hook timed out after 600 seconds
"hookName": "auto-format",
"timeout": 600
或 Claude 执行工具前长时间等待,无任何输出。
根因
Hook 脚本执行时间超过 timeout 设置(默认 600 秒),Claude Code 会终止脚本并记录超时错误。
常见原因:
- 网络请求:脚本中调用了外部 API 但目标服务不可达
- 无限循环:脚本逻辑错误导致死循环
- 未设置 timeout:默认 600 秒太长,实际上几秒就该完成的操作
一览对比表
| 对比维度 | 正常情况 | 异常情况 |
|---|---|---|
| 执行时间 | < 5 秒 | > timeout 值 |
| timeout 设置 | 根据操作类型合理设置 | 使用默认 600 秒 |
| 外部依赖 | 可达 | 网络超时或服务宕机 |
代码修复
设置合理的 timeout:
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/auto-format.sh\"",
"timeout": 30
}
| 场景 | 推荐 timeout |
|---|---|
| 简单判断(文件路径检查) | 5-10 秒 |
| 代码格式化(ruff/prettier) | 30 秒 |
| 运行测试套件 | 120-300 秒 |
| 网络请求 | 15-30 秒 |
给脚本加超时保护:
#!/bin/bash
# 使用 timeout 命令限制外部工具执行时间
timeout 20 ruff format "$1" 2>/dev/null
exit 0
验证
# 在 /hooks 菜单中确认 timeout 值已更新
/hooks
十三、Debug #5 — additionalContext 未注入 Claude 上下文
报错日志
Hook 执行成功(退出码 0),JSON 输出正确,但 Claude 似乎看不到注入的上下文信息。
根因
additionalContext 未生效的原因:
- JSON 格式错误:
hookEventName必须与实际事件名完全匹配(如"PreToolUse"而非"pretooluse") - stdout 混入非 JSON 文本:Claude Code 只解析纯 JSON,如果 stdout 中包含其他文本则整个输出被忽略
- 超长输出被截断:输出超过 10,000 字符时会被写入文件,Claude 只收到文件路径
一览对比表
| 对比维度 | 正常情况 | 异常情况 |
|---|---|---|
| hookEventName | "PostToolUse"(与事件完全匹配) | "posttooluse"(大小写不对) |
| stdout 内容 | 仅包含合法 JSON | JSON 前有 echo 输出的文本 |
| 输出长度 | < 10,000 字符 | 超过 10,000 字符被截断为文件 |
代码修复
正确的 JSON 输出格式:
#!/bin/bash
# ... 业务逻辑 ...
# 输出纯 JSON,不混入其他文本
jq -n '{
hookSpecificOutput: {
hookEventName: "PostToolUse",
additionalContext: "当前分支: main. 最后提交: fix: auth bug"
}
}'
exit 0
避免 stdout 污染:
#!/bin/bash
# 所有调试信息输出到 stderr
echo "Debug: processing file" >&2
# stdout 只输出最终 JSON
jq -n '{ hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: "info" } }'
验证
# 使用 --debug 模式查看 Hook 输出
claude --debug
# 让 Claude 执行匹配的操作
# 在调试日志中检查 Hook 的 stdout 是否为纯 JSON
十四、速查卡
14.1 核心事件速查
| 事件 | 触发时机 | 能阻止? | 典型用途 |
|---|---|---|---|
SessionStart | 会话启动/恢复 | ❌ | 加载 Git 状态、设置环境变量 |
UserPromptSubmit | 用户提交提示 | ✅ | 提示验证、自动注入上下文 |
PreToolUse | 工具执行前 | ✅ | 拦截危险命令、保护敏感文件 |
PostToolUse | 工具执行后 | ❌ | 自动格式化、审计日志 |
Stop | Claude 想停止 | ✅ | 强制运行测试、检查完成度 |
SessionEnd | 会话终止 | ❌ | 清理临时文件、发送通知 |
14.2 匹配器语法速查
| 匹配器 | 匹配范围 |
|---|---|
Bash | 仅 Bash 工具 |
Edit|Write | Edit 或 Write |
mcp__memory__.* | Memory MCP 服务器所有工具 |
^Notebook | 以 Notebook 开头的工具 |
* / 省略 | 所有工具 |
14.3 退出码速查
| 退出码 | 含义 | stdout 处理 |
|---|---|---|
0 | 放行 | 解析 JSON(如有) |
2 | 阻止 | 忽略 JSON,stderr 展示给 Claude |
| 其他 | 非阻塞错误 | 忽略 JSON,stderr 展示 |
14.4 JSON 输出字段速查
| 字段 | 适用事件 | 说明 |
|---|---|---|
continue: false | 所有 | 停止 Claude |
stopReason | 所有 | continue=false 时的原因 |
decision: "block" | Stop, PostToolUse, UserPromptSubmit 等 | 阻止特定行为 |
hookSpecificOutput.additionalContext | 大部分 | 注入上下文到 Claude |
hookSpecificOutput.permissionDecision | PreToolUse | allow / deny / ask / defer |
hookSpecificOutput.updatedInput | PreToolUse | 修改工具输入 |
14.5 文件路径汇总
| 文件 | 路径 | 用途 |
|---|---|---|
| 全局配置 | C:\Users\<用户名>\.claude\settings.json | 全局 Hook |
| 项目配置 | <项目>\.claude\settings.json | 项目共享 Hook |
| 项目本地 | <项目>\.claude\settings.local.json | 个人 Hook(不提交) |
| 项目脚本目录 | <项目>\.claude\hooks\ | Hook 脚本存放 |
| 调试日志 | claude --debug 输出 | Hook 执行详情 |
14.6 常见报错 → 解决方案
| 报错特征 | 根因 | 解决 |
|---|---|---|
| Hook 无任何效果 | matcher 大小写不对 / 路径未加引号 | 检查 matcher(Bash 非 bash)→ Debug #1 |
JSON validation failed | stdout 混入非 JSON 文本 | 调试输出重定向到 stderr → Debug #2 |
No such file or directory | Windows 无 bash / jq | 用 Python 替代 → Debug #3 |
Hook timed out | 脚本执行过久 | 设置合理 timeout → Debug #4 |
| 上下文未注入 | hookEventName 不匹配 / stdout 污染 | 确保纯 JSON 输出 → Debug #5 |
disableAllHooks 无效 | managed policy 优先级更高 | 在 managed-settings.json 中设置 |
| Hook 重复执行 | 多个配置层级都定义了相同 Hook | 检查全局 + 项目 + 本地三层配置 |
参考文献
- Hooks reference — Claude Code 官方文档
- Automate workflows with hooks — Claude Code 官方指南
- security-guidance plugin — Anthropic 官方插件
- hookify plugin — Anthropic 官方插件
- Claude Code Plugins README — Anthropic GitHub
- Claude Code settings reference — 官方文档
- tdd-guard — TDD 铁律守护插件 — PreToolUse + Stop 双事件强制 TDD 流程
- claude-code-safety-net — 语义级命令拦截 — 支持 5 层 shell 包装检测
- claude-code-prompt-improver — 提示词质量提升 — UserPromptSubmit 拦截模糊提问
- claude-hook-cookbook — 9 个零依赖 Hook 脚本 — 覆盖格式化、lint、安全、日志、通知等场景
- pilot-shell — 完整工程化平台 — 四重质量门禁 + token 优化
- claude-code-infrastructure-showcase — 生产级 Hook 模式 — 6 个月生产验证
- awesome-claude-code — 社区生态总表 — Skills / Hooks / Plugins / Agents 精选列表