npx skills add https://github.com/maxoreric/sop-engine --skill hook-skillSKILL.md
Hook 开发
创建和配置 Claude Code Hooks,用于在特定事件时执行自定义逻辑。
Hook 基础
什么是 Hook?
Hook 是在 Claude Code 执行过程中特定时机自动触发的脚本或命令。可以用于:
- 验证和拦截工具调用
- 自动批准特定操作
- 添加上下文信息
- 实现循环执行
- 通知和日志记录
10 种 Hook 事件
| 事件 | 触发时机 | 常见用途 |
|---|---|---|
| PreToolUse | 工具执行前 | 验证、拦截、修改输入 |
| PostToolUse | 工具执行后 | 验证结果、触发后续 |
| PermissionRequest | 权限对话框显示时 | 自动批准/拒绝 |
| UserPromptSubmit | 用户提交 prompt 时 | 添加上下文、验证 |
| Stop | Claude 完成响应时 | 实现循环、强制继续 |
| SubagentStop | Subagent 完成时 | 链式触发、验证完成 |
| PreCompact | 压缩操作前 | 保存状态 |
| SessionStart | 会话开始时 | 加载上下文、初始化 |
| SessionEnd | 会话结束时 | 清理、保存状态 |
| Notification | 通知时 | 自定义通知 |
Exit Code 含义
| Exit Code | 含义 | 效果 |
|---|---|---|
| 0 | 成功 | 继续执行,stdout 可返回 JSON |
| 2 | 阻止 | 阻止操作,stderr 反馈给 Claude |
| 其他 | 非阻止错误 | 显示警告,继续执行 |
配置结构
hooks.json 格式
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here",
"timeout": 60
}
]
}
]
}
}
字段说明
- matcher:工具名匹配模式(仅 PreToolUse、PostToolUse、PermissionRequest)
- 精确匹配:
Write - 正则匹配:
Edit|Write、Notebook.* - 匹配所有:
*或省略
- 精确匹配:
- type:
command(bash 命令)或prompt(LLM 评估) - command:要执行的命令
- timeout:超时时间(秒),默认 60
环境变量
$CLAUDE_PROJECT_DIR:项目根目录$CLAUDE_PLUGIN_ROOT:插件根目录(插件 Hook 专用)$CLAUDE_ENV_FILE:环境变量文件(仅 SessionStart)
Hook 输入(stdin JSON)
通用字段
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/directory",
"permission_mode": "default",
"hook_event_name": "PreToolUse"
}
PreToolUse 输入
{
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.txt",
"content": "file content"
},
"tool_use_id": "toolu_01ABC123..."
}
Stop 输入
{
"hook_event_name": "Stop",
"stop_hook_active": true // 是否已被 Stop hook 继续过
}
UserPromptSubmit 输入
{
"hook_event_name": "UserPromptSubmit",
"prompt": "用户的 prompt 内容"
}
SessionStart 输入
{
"hook_event_name": "SessionStart",
"source": "startup" // startup | resume | clear | compact
}
Hook 输出(JSON)
PreToolUse 控制
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow", // allow | deny | ask
"permissionDecisionReason": "原因",
"updatedInput": { } // 可选:修改输入
}
}
Stop 控制
{
"decision": "block", // block = 阻止停止,继续执行
"reason": "告诉 Claude 为什么要继续"
}
UserPromptSubmit 控制
{
"decision": "block", // 可选:阻止 prompt
"reason": "阻止原因",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "添加的上下文"
}
}
SessionStart 控制
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "启动时添加的上下文"
}
}
常用模式
1. 实现循环执行(Stop Hook)
#!/bin/bash
# stop-loop.sh - 阻止停止,让 Claude 继续
HOOK_INPUT=$(cat)
STOP_ACTIVE=$(echo "$HOOK_INPUT" | jq -r '.stop_hook_active // false')
# 检查状态文件
if [ -f ".loop-status.json" ]; then
CURRENT=$(jq -r '.current // 0' .loop-status.json)
MAX=$(jq -r '.max // 5' .loop-status.json)
if [ "$CURRENT" -lt "$MAX" ]; then
# 更新计数
jq ".current = $((CURRENT + 1))" .loop-status.json > .tmp && mv .tmp .loop-status.json
# 阻止停止
echo "继续执行 ($((CURRENT + 1))/$MAX)" >&2
exit 2
fi
fi
exit 0
2. 自动批准文档文件读取
#!/usr/bin/env python3
import json
import sys
input_data = json.load(sys.stdin)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
if tool_name == "Read":
file_path = tool_input.get("file_path", "")
if file_path.endswith((".md", ".txt", ".json")):
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "文档文件自动批准"
}
}
print(json.dumps(output))
sys.exit(0)
sys.exit(0)
3. 添加时间上下文(SessionStart)
#!/bin/bash
# session-start.sh
echo "当前时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "项目目录: $CLAUDE_PROJECT_DIR"
# 如果有 CLAUDE_ENV_FILE,设置环境变量
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo "export PROJECT_START_TIME=$(date +%s)" >> "$CLAUDE_ENV_FILE"
fi
exit 0
4. 验证 Bash 命令(PreToolUse)
#!/usr/bin/env python3
import json
import sys
import re
input_data = json.load(sys.stdin)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
if tool_name != "Bash":
sys.exit(0)
command = tool_input.get("command", "")
# 危险命令检查
dangerous_patterns = [
(r"\brm\s+-rf\s+/", "禁止删除根目录"),
(r"\bsudo\b", "禁止使用 sudo"),
]
for pattern, message in dangerous_patterns:
if re.search(pattern, comman
...
Repository
maxoreric/sop-engineParent repository
Repository Stats
Stars0
Forks0