名称:claude-code-hooks 描述:为Claude Code自动化创建事件驱动钩子。配置设置或frontmatter中的钩子事件,解析stdin JSON输入,返回决策控制JSON,并实现安全钩子脚本。
Claude Code 钩子 — 元参考
这个技能提供创建Claude Code钩子的权威参考。在构建Claude Code事件触发的自动化时使用此技能。
何时使用此技能
- 为Claude Code构建事件驱动自动化
- 创建PreToolUse防护以阻止危险命令
- 实现PostToolUse格式化器、linter或审核器
- 添加Stop钩子用于测试或通知
- 设置SessionStart/SessionEnd用于环境管理
- 将Claude Code与CI/CD管道集成(无头模式)
快速参考
| 事件 | 触发器 | 用例 |
|---|---|---|
SessionStart |
会话开始/恢复 | 初始化环境 |
UserPromptSubmit |
用户提交提示 | 预处理/验证输入 |
PreToolUse |
工具执行前 | 验证、阻止危险命令 |
PermissionRequest |
权限对话框显示 | 自动允许/拒绝权限 |
PostToolUse |
工具成功后 | 格式化、审核、通知 |
PostToolUseFailure |
工具失败后 | 捕获失败、添加指导 |
SubagentStart |
子代理生成 | 检查子代理元数据 |
Stop |
Claude完成时 | 运行测试、总结 |
SubagentStop |
子代理完成 | 验证子代理完成 |
Notification |
通知时 | 警报集成 |
PreCompact |
上下文压缩前 | 保留关键上下文 |
Setup |
--init/--maintenance |
初始化仓库/环境 |
SessionEnd |
会话结束 | 清理、保存状态 |
钩子结构
.claude/hooks/
├── pre-tool-validate.sh
├── post-tool-format.sh
├── post-tool-audit.sh
├── stop-run-tests.sh
└── session-start-init.sh
配置
settings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-format.sh"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/pre-tool-validate.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/stop-run-tests.sh"
}
]
}
]
}
}
执行模型(2026年1月)
- 钩子通过stdin接收JSON有效负载(视为不受信任的输入),并以用户权限运行(在Bash工具沙箱外)。
- 默认超时为每个钩子命令60秒;所有匹配的钩子并行运行;相同的命令被去重。
钩子输入(stdin)
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "ls -la"
}
}
环境变量(shell)
| 变量 | 描述 |
|---|---|
CLAUDE_PROJECT_DIR |
Claude Code启动的绝对项目根目录 |
CLAUDE_PLUGIN_ROOT |
插件根目录(仅插件钩子) |
CLAUDE_CODE_REMOTE |
在远程/网络环境中为"true";否则为空/本地 |
CLAUDE_ENV_FILE |
持久化export ...行的文件路径(在SessionStart中可用;检查文档以获取Setup支持) |
退出代码
| 代码 | 含义 | 注释 |
|---|---|---|
0 |
成功 | 写入stdout的JSON被解析为结构化控制 |
2 |
阻塞错误 | stderr成为消息;stdout中的JSON被忽略 |
| 其他 | 非阻塞错误 | 执行继续;stderr在详细模式中可见 |
Stdout注入说明:对于UserPromptSubmit、SessionStart和Setup,非JSON stdout(退出0)被注入到Claude的上下文中;大多数其他事件仅在详细模式中显示stdout。
决策控制 + 输入修改(v2.0.10+)
PreToolUse钩子可以允许/拒绝/询问,并可选地通过updatedInput修改工具输入。
钩子输出模式
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "向用户显示的原因(以及在拒绝时向Claude显示)",
"updatedInput": { "command": "echo 'modified'" },
"additionalContext": "在工具运行前添加的额外上下文"
}
}
注意:旧的decision/reason字段已弃用;推荐使用hookSpecificOutput.*字段。
示例:重定向敏感文件编辑
#!/bin/bash
set -euo pipefail
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')"
# 重定向package-lock.json编辑到/dev/null
if [[ "$FILE_PATH" == *"package-lock.json" ]]; then
UPDATED_INPUT="$(echo "$INPUT" | jq -c '.tool_input | .file_path = "/dev/null"')"
jq -cn --argjson updatedInput "$UPDATED_INPUT" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
permissionDecisionReason: "重定向写入到/dev/null",
updatedInput: $updatedInput
}
}'
exit 0
fi
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
示例:从Git Add中剥离敏感文件
#!/bin/bash
set -euo pipefail
INPUT="$(cat)"
TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name')"
CMD="$(echo "$INPUT" | jq -r '.tool_input.command // empty')"
if [[ "$TOOL_NAME" == "Bash" && "$CMD" =~ ^git[[:space:]]+add ]]; then
# 从暂存中移除.env文件
SAFE_CMD="$(echo "$CMD" | sed 's/\.env[^ ]*//g')"
if [[ "$SAFE_CMD" != "$CMD" ]]; then
echo '{}' | jq -cn --arg cmd "$SAFE_CMD" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
permissionDecisionReason: "从git add中移除.env",
updatedInput: { command: $cmd }
}
}'
exit 0
fi
fi
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
基于提示的钩子
对于复杂决策,使用LLM评估的钩子(type: "prompt")而不是bash脚本。它们对于Stop和SubagentStop决策最有用。
配置
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "评估Claude是否应停止。上下文JSON:$ARGUMENTS。返回{\"ok\": true}如果所有任务完成,否则{\"ok\": false, \"reason\": \"剩余内容\"}。",
"timeout": 30
}
]
}
]
}
}
响应模式
- 允许:
{"ok": true} - 阻止:
{"ok": false, "reason": "向Claude显示的解释"}
组合命令和提示钩子
使用命令钩子进行快速、确定性检查。使用提示钩子进行细微决策:
{
"Stop": [
{
"hooks": [
{ "type": "command", "command": ".claude/hooks/quick-check.sh" },
{ "type": "prompt", "prompt": "验证代码质量符合标准" }
]
}
]
}
钩子模板
预工具验证
#!/bin/bash
set -euo pipefail
INPUT="$(cat)"
TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name')"
CMD="$(echo "$INPUT" | jq -r '.tool_input.command // empty')"
if [[ "$TOOL_NAME" == "Bash" ]]; then
# 阻止rm -rf /
if echo "$CMD" | grep -qE 'rm\s+-rf\s+/'; then
echo '{}' | jq -cn '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "检测到危险的rm命令"
}
}'
exit 0
fi
# 阻止强制推送到main
if echo "$CMD" | grep -qE 'git\s+push.*--force.*(main|master)'; then
echo '{}' | jq -cn '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "不允许强制推送到main/master"
}
}'
exit 0
fi
# 软警告:可能的凭据暴露
if echo "$CMD" | grep -qE '(password|secret|api_key)\s*='; then
echo '{}' | jq -cn '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "ask",
permissionDecisionReason: "命令中可能包含凭据暴露",
additionalContext: "命令可能包含秘密。确认意图并避免提交秘密。"
}
}'
exit 0
fi
fi
exit 0
后工具格式化
#!/bin/bash
set -euo pipefail
INPUT="$(cat)"
TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name')"
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')"
if [[ "$TOOL_NAME" =~ ^(Edit|Write)$ && -n "$FILE_PATH" && -f "$FILE_PATH" ]]; then
case "$FILE_PATH" in
*.js|*.ts|*.jsx|*.tsx|*.json|*.md)
npx prettier --write "$FILE_PATH" 2>/dev/null || true
;;
*.py)
ruff format "$FILE_PATH" 2>/dev/null || true
;;
*.go)
gofmt -w "$FILE_PATH" 2>/dev/null || true
;;
*.rs)
rustfmt "$FILE_PATH" 2>/dev/null || true
;;
esac
fi
exit 0
后工具安全审核
#!/bin/bash
set -euo pipefail
INPUT="$(cat)"
TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name')"
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')"
if [[ "$TOOL_NAME" =~ ^(Edit|Write)$ && -n "$FILE_PATH" && -f "$FILE_PATH" ]]; then
# 检查硬编码的秘密
if grep -qE '(password|secret|api_key|token)\s*[:=]\s*["\x27][^"\x27]+["\x27]' "$FILE_PATH"; then
echo "警告:$FILE_PATH中可能包含硬编码秘密" >&2
fi
# 检查生产代码中的console.log
if [[ "$FILE_PATH" =~ \.(ts|js|tsx|jsx)$ ]] && grep -q 'console.log' "$FILE_PATH"; then
echo "注意:$FILE_PATH中找到console.log" >&2
fi
fi
exit 0
停止钩子(运行测试)
#!/bin/bash
set -euo pipefail
# 在Claude完成后运行测试
cd "$CLAUDE_PROJECT_DIR"
# 检测测试框架
if [[ -f "package.json" ]]; then
if grep -q '"vitest"' package.json; then
npm run test 2>&1 | head -50
elif grep -q '"jest"' package.json; then
npm test 2>&1 | head -50
fi
elif [[ -f "pytest.ini" ]] || [[ -f "pyproject.toml" ]]; then
pytest --tb=short 2>&1 | head -50
fi
exit 0
会话开始
#!/bin/bash
set -euo pipefail
cd "$CLAUDE_PROJECT_DIR"
# 检查git状态
echo "=== Git状态 ==="
git status --short
# 检查未提交的更改
if ! git diff --quiet; then
echo "警告:检测到未提交的更改"
fi
# 验证依赖
if [[ -f "package.json" ]]; then
if [[ ! -d "node_modules" ]]; then
echo "注意:node_modules缺失,运行npm install"
fi
fi
exit 0
匹配器
匹配器筛选触发钩子的工具:
- 精确匹配:
Write仅匹配Write工具 - 正则表达式:
Edit|Write或Notebook.* - 匹配所有:
*(也适用于""或省略匹配器)
安全最佳实践
钩子安全检查清单
[ ] 使用正则表达式验证所有输入
[ ] 引用所有变量:"$VAR" 而不是 $VAR
[ ] 使用绝对路径
[ ] 不使用不受信任输入的eval
[ ] 在顶部设置 -euo pipefail
[ ] 保持钩子快速(<1秒)
[ ] 记录操作以进行审计
[ ] 部署前手动测试
钩子组合
同一事件上的多个钩子
{
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": ".claude/hooks/format.sh" },
{ "type": "command", "command": ".claude/hooks/audit.sh" },
{ "type": "command", "command": ".claude/hooks/notify.sh" }
]
}
]
}
所有匹配的钩子并行运行。如果您需要严格的顺序(格式化 → lint → 测试),请创建一个按顺序运行它们的包装脚本。
调试钩子
# 手动测试PostToolUse钩子(stdin JSON)
export CLAUDE_PROJECT_DIR="$(pwd)"
echo '{"hook_event_name":"PostToolUse","tool_name":"Edit","tool_input":{"file_path":"'"$(pwd)"'/src/app.ts"}}' \
| bash .claude/hooks/post-tool-format.sh
# 检查退出代码
echo $?
导航
资源
- references/hook-patterns.md — 常见模式
- references/hook-security.md — 安全指南
- data/sources.json — 文档链接
相关技能
- …/claude-code-commands/SKILL.md — 命令创建
- …/claude-code-agents/SKILL.md — 代理创建
- …/ops-devops-platform/SKILL.md — CI/CD集成