Skills 开发:SKILL.md 全参数 / 动态上下文注入 / 调用控制矩阵
第四十二章:Claude Code Hooks:pre-tool、post-tool 与自动化质量门控
42.1 Hooks 的设计动机
Claude Code 是一个强大的 AI 编程助手,但在团队和生产环境中使用时,你需要一种机制来确保 Claude 的每个操作都符合规范——不只是通过 CLAUDE.md 告知 Claude 规范是什么,而是在行为发生的前后自动强制执行这些规范。
这就是 Claude Code Hooks 的存在价值。
Hooks 是在 Claude 执行工具调用前后触发的 Shell 命令钩子。它们在 .claude/settings.json 中配置,完全独立于 Claude 的 AI 决策之外运行。无论 Claude 决定做什么,Hooks 都会在定义的节点执行你指定的命令。
核心价值:Hooks 把团队规范从"建议"变成了"强制执行"。
考虑这样一个场景:你的团队规定所有 Python 文件修改后必须运行 Black 格式化。有了 post-tool Hooks,每当 Claude 写入或修改 .py 文件后,Black 就会自动运行——不需要提醒 Claude,不需要依赖 Claude 的主动性,格式化就自动发生了。
42.2 Hooks 的配置格式
Hooks 在 .claude/settings.json 中配置。这个文件是 Claude Code 的项目级配置文件,应该提交到版本控制:
{
"hooks": {
"pre-tool": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "echo 'About to modify files...' >> .claude/hooks.log"
}
]
}
],
"post-tool": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "scripts/post-write-hook.sh"
}
]
}
]
}
}
配置结构解析
hooks
├── pre-tool # 工具执行前触发的 Hooks 列表
│ └── [Hook Entry]
│ ├── matcher # 匹配哪些工具(正则表达式)
│ └── hooks # 要执行的命令列表
│ └── [Hook]
│ ├── type # 目前只支持 "command"
│ └── command # 要执行的 Shell 命令
└── post-tool # 工具执行后触发的 Hooks 列表
└── [同结构]
matcher 的写法
matcher 是一个正则表达式字符串,用于匹配工具名称。Claude Code 的主要工具名称如下:
文件操作类:
- Write # 写入新文件
- Edit # 编辑现有文件(单处替换)
- MultiEdit # 多处编辑
- Read # 读取文件
Shell 操作类:
- Bash # 执行 Shell 命令
搜索类:
- Grep # 文本搜索
- Glob # 文件名搜索
其他:
- WebFetch # 获取网页内容
- TodoWrite # 写入 Todo 列表
Matcher 示例:
"matcher": "Write|Edit|MultiEdit" // 匹配所有写操作
"matcher": "Bash" // 只匹配 Bash 工具
"matcher": ".*" // 匹配所有工具
"matcher": "Write" // 只匹配 Write
42.3 pre-tool Hooks:在操作前执行
pre-tool Hooks 在 Claude 执行工具之前触发。它们的主要用途是:
- 预检查:验证前置条件是否满足
- 权限验证:检查敏感操作的权限
- 通知:在重要操作前发送通知
- 审计日志:记录即将发生的操作
示例一:防止在主分支直接提交
{
"hooks": {
"pre-tool": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "scripts/check-branch.sh"
}
]
}
]
}
}
scripts/check-branch.sh:
#!/bin/bash
# 检查当前 git 分支,如果是 main 或 master,输出警告
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
echo "WARNING: You are on the $BRANCH branch. Direct commits to $BRANCH are discouraged."
echo "Consider creating a feature branch first: git checkout -b feature/your-feature"
# 返回非零退出码会阻止工具执行(Claude Code 会中止并向用户报告)
# 这里我们只是警告,不阻止:
exit 0
fi
exit 0
示例二:记录所有文件操作审计日志
{
"hooks": {
"pre-tool": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "echo \"$(date -Iseconds) PRE-TOOL: $CLAUDE_TOOL_NAME $CLAUDE_TOOL_INPUT\" >> .claude/audit.log"
}
]
}
]
}
}
关于 pre-tool 的退出码
pre-tool Hook 的退出码有重要含义:
- 退出码 0:Hook 成功,允许工具继续执行
- 退出码非 0:Hook 失败,Claude Code 会阻止该工具执行并向用户报告错误
这让 pre-tool Hooks 成为真正的"质量门控"——你可以编写检查脚本,在不满足条件时直接阻止 Claude 的操作。
示例三:阻止修改生产配置文件
#!/bin/bash
# pre-write-check.sh
# 阻止 Claude 修改特定的生产配置文件
PROTECTED_FILES=(
"config/production.yaml"
"config/production.json"
".env.production"
"kubernetes/prod/"
)
# 从环境变量读取 Claude 即将操作的文件路径
TARGET_FILE="${CLAUDE_TOOL_INPUT_FILE_PATH:-}"
for protected in "${PROTECTED_FILES[@]}"; do
if [[ "$TARGET_FILE" == *"$protected"* ]]; then
echo "ERROR: Modification of protected production config file is not allowed."
echo "File: $TARGET_FILE"
echo "To modify production configs, use the deployment pipeline."
exit 1 # 非零退出码,阻止操作
fi
done
exit 0
42.4 post-tool Hooks:在操作后执行
post-tool Hooks 在 Claude 的工具执行完成后触发。它们的主要用途是:
- 自动格式化:代码写入后立即格式化
- 自动测试:代码修改后立即运行相关测试
- 自动校验:验证修改是否符合规范
- 构建触发:代码变更后触发增量构建
- 通知:操作完成后发送通知
示例一:Python 文件自动格式化
{
"hooks": {
"post-tool": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "scripts/post-write-format.sh"
}
]
}
]
}
}
scripts/post-write-format.sh:
#!/bin/bash
# 对 Python 文件执行自动格式化和 lint
# 获取被修改的文件(从环境变量)
MODIFIED_FILE="${CLAUDE_TOOL_INPUT_FILE_PATH:-}"
if [[ "$MODIFIED_FILE" == *.py ]]; then
echo "Running Black formatter on $MODIFIED_FILE..."
black "$MODIFIED_FILE" 2>&1
echo "Running isort on $MODIFIED_FILE..."
isort "$MODIFIED_FILE" 2>&1
echo "Running flake8 lint check..."
flake8 "$MODIFIED_FILE" 2>&1
LINT_EXIT=$?
if [ $LINT_EXIT -ne 0 ]; then
echo "WARNING: flake8 found issues in $MODIFIED_FILE"
# post-tool 的非零退出码通常不会阻止 Claude,但会在输出中显示
fi
fi
exit 0
示例二:TypeScript 文件类型检查
{
"hooks": {
"post-tool": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "scripts/ts-typecheck.sh"
}
]
}
]
}
}
scripts/ts-typecheck.sh:
#!/bin/bash
# 对修改的 TypeScript 文件执行类型检查
MODIFIED_FILE="${CLAUDE_TOOL_INPUT_FILE_PATH:-}"
if [[ "$MODIFIED_FILE" == *.ts ]] || [[ "$MODIFIED_FILE" == *.tsx ]]; then
echo "Running TypeScript type check..."
# 使用 tsc --noEmit 检查类型,不生成输出文件
npx tsc --noEmit --project tsconfig.json 2>&1
TS_EXIT=$?
if [ $TS_EXIT -ne 0 ]; then
echo "TypeScript type errors found. Claude should review and fix."
exit 1 # 通知 Claude 有问题需要处理
fi
fi
exit 0
当 post-tool Hook 返回非零退出码时,Claude Code 会将错误信息反馈给 Claude,Claude 可以据此继续修复问题。这形成了一个自动反馈循环:Claude 修改代码 → Hook 运行类型检查 → 发现错误 → 反馈给 Claude → Claude 继续修复。
42.5 环境变量:Hook 如何获取上下文
Hooks 通过环境变量获取关于当前操作的上下文信息:
# Claude Code 注入到 Hook 进程的环境变量
CLAUDE_TOOL_NAME # 触发 Hook 的工具名称(如 "Write")
CLAUDE_TOOL_INPUT # 工具的完整输入(JSON 格式)
CLAUDE_TOOL_INPUT_FILE_PATH # 对于文件操作,目标文件路径
CLAUDE_TOOL_INPUT_COMMAND # 对于 Bash 工具,要执行的命令
CLAUDE_TOOL_OUTPUT # (仅 post-tool)工具的输出结果
CLAUDE_SESSION_ID # 当前会话 ID
CLAUDE_PROJECT_DIR # 项目根目录路径
解析 JSON 输入
对于需要更详细信息的 Hook,可以解析 CLAUDE_TOOL_INPUT JSON:
#!/bin/bash
# 解析工具输入以获取详细信息
TOOL_INPUT="${CLAUDE_TOOL_INPUT:-{}}"
# 使用 jq 解析 JSON
FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.file_path // empty')
CONTENT=$(echo "$TOOL_INPUT" | jq -r '.content // empty')
COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command // empty')
echo "Tool: $CLAUDE_TOOL_NAME"
echo "File: $FILE_PATH"
echo "Command: $COMMAND"
42.6 构建自动化质量门控系统
通过组合 pre-tool 和 post-tool Hooks,可以构建一套完整的自动化质量门控系统。以下是一个面向 Node.js 项目的完整配置示例:
{
"hooks": {
"pre-tool": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "scripts/hooks/pre-bash.sh"
}
]
},
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "scripts/hooks/pre-write.sh"
}
]
}
],
"post-tool": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "scripts/hooks/post-write.sh"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "scripts/hooks/post-bash.sh"
}
]
}
]
}
}
scripts/hooks/post-write.sh(核心质量门控):
#!/bin/bash
set -e
MODIFIED_FILE="${CLAUDE_TOOL_INPUT_FILE_PATH:-}"
EXIT_CODE=0
# 1. JavaScript/TypeScript 文件处理
if [[ "$MODIFIED_FILE" =~ \.(js|jsx|ts|tsx)$ ]]; then
# 自动格式化(不是 lint,Prettier 会直接修改文件)
if command -v prettier &>/dev/null; then
prettier --write "$MODIFIED_FILE" 2>&1
echo "✓ Prettier formatting applied"
fi
# ESLint 检查(返回 lint 结果,不自动修复)
if command -v eslint &>/dev/null; then
eslint "$MODIFIED_FILE" 2>&1
ESLINT_EXIT=$?
if [ $ESLINT_EXIT -ne 0 ]; then
echo "✗ ESLint found issues"
EXIT_CODE=$ESLINT_EXIT
else
echo "✓ ESLint passed"
fi
fi
fi
# 2. 测试文件检测(如果修改了业务文件,检查对应测试是否存在)
if [[ "$MODIFIED_FILE" =~ src/(.+)\.(ts|js)$ ]] && [[ ! "$MODIFIED_FILE" =~ \.(test|spec)\. ]]; then
RELATIVE_PATH="${BASH_REMATCH[1]}"
TEST_FILE="src/${RELATIVE_PATH}.test.ts"
SPEC_FILE="src/${RELATIVE_PATH}.spec.ts"
if [ ! -f "$TEST_FILE" ] && [ ! -f "$SPEC_FILE" ]; then
echo "WARNING: No test file found for $MODIFIED_FILE"
echo "Consider creating: $TEST_FILE"
fi
fi
# 3. package.json 变更检测
if [[ "$MODIFIED_FILE" == "package.json" ]]; then
echo "package.json modified. Running npm install to sync node_modules..."
npm install 2>&1
fi
exit $EXIT_CODE
42.7 Hooks 与 CLAUDE.md 的协同
Hooks 和 CLAUDE.md 是互补的工具,各自解决不同层面的问题:
| 维度 | CLAUDE.md | Hooks |
|---|---|---|
| 执行方式 | 影响 Claude 的 AI 决策 | 独立于 AI,Shell 命令执行 |
| 可靠性 | 依赖 Claude 遵守 | 机械强制执行 |
| 适用场景 | 风格指南、架构决策 | 格式化、lint、测试 |
| 可绕过性 | Claude 可能忘记或忽略 | 无法被忽略 |
| 灵活性 | 高(自然语言描述) | 低(必须是可执行脚本) |
最佳实践是两者结合:
- CLAUDE.md 告知 Claude 规范和意图
- Hooks 强制执行可以自动化的质量检查
例如,CLAUDE.md 说"所有 TypeScript 文件必须通过类型检查",同时配置 post-tool Hook 在每次文件写入后自动运行 tsc --noEmit。这样即使 Claude 忘记了规范,Hook 也会捕获问题并反馈。
42.8 调试 Hooks
Hooks 出问题时,调试可能会比较困难。以下是一些实用技巧:
启用 Hook 日志
# 在 Hook 脚本开头添加日志
LOG_FILE=".claude/hooks-debug.log"
echo "$(date -Iseconds) HOOK: $CLAUDE_TOOL_NAME" >> "$LOG_FILE"
echo " INPUT: $CLAUDE_TOOL_INPUT" >> "$LOG_FILE"
在 CLAUDE.md 中说明调试方法
## Hook 调试
如果 Hooks 出现问题,检查 .claude/hooks-debug.log 文件。
运行 `scripts/hooks/test-hooks.sh` 可以在不涉及 Claude 的情况下测试所有 Hook。
本地测试 Hook 脚本
# 模拟 Claude Code 的环境变量,直接测试 Hook
CLAUDE_TOOL_NAME="Write" \
CLAUDE_TOOL_INPUT_FILE_PATH="src/utils/format.ts" \
CLAUDE_TOOL_INPUT='{"file_path":"src/utils/format.ts","content":"export function foo() {}"}' \
bash scripts/hooks/post-write.sh
小结
Claude Code Hooks 是将 AI 辅助编程从"随机质量"提升为"可预测质量"的关键机制。
关键要点:
- Hooks 在
.claude/settings.json中配置,是独立于 AI 决策的 Shell 命令 - pre-tool Hooks 可以在操作前检查条件,返回非零退出码可阻止操作
- post-tool Hooks 可以在操作后自动格式化、类型检查、运行测试
- 通过环境变量(
CLAUDE_TOOL_NAME、CLAUDE_TOOL_INPUT_FILE_PATH等)获取操作上下文 - Hooks 与 CLAUDE.md 互补:前者负责 AI 决策层,后者负责机械执行层
- 结合 pre-tool 和 post-tool Hooks 可以构建完整的自动化质量门控系统