Hook 功能使用文档
📑 目录
概述
Hook 功能允许您在 AI Agent 执行的关键节点插入自定义脚本,实现对 Agent 行为的精细控制。Hook 机制完全兼容 Claude Code Hooks 规范,提供了一种强大且灵活的扩展方式。
功能特性
- ✅ 多事件支持: 支持 7 种关键事件(SessionStart、SessionEnd、PreToolUse、PostToolUse、UserPromptSubmit、Stop、PreCompact)
- ✅ 工具拦截: 在工具执行前后进行验证、修改或阻止
- ✅ 上下文注入: 在会话不同阶段动态注入额外上下文
- ✅ 并行执行: 多个 Hook 自动并行执行,提升性能
- ✅ 自动去重: 相同命令自动去重,避免重复执行
- ✅ 灵活配置: 支持正则匹配、超时控制、项目级/用户级配置
- ✅ 会话跟踪: 智能识别会话变化,避免重复触发 SessionStart
- ✅ 安全可靠: 完善的错误处理和超时机制
支持的 Hook 事件
1. SessionStart - 会话启动
触发时机: 会话开始时(每个新会话只触发一次)
触发逻辑:
- 系统通过对比
conversationId判断是否为新会话 - 同一会话中多次请求不会重复触发
- 切换到新会话或清空会话后会重新触发
用途:
- 初始化项目环境
- 注入项目特定上下文
- 设置会话级别的配置
- 加载项目规范和文档
Matcher 匹配字段: source
startup- 首次启动(目前仅支持此值)
输入数据 (stdin JSON):
json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.txt",
"cwd": "/project/path",
"hook_event_name": "SessionStart",
"source": "startup"
}输出数据 (stdout JSON):
json
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "项目使用 TypeScript + React,请优先使用函数式组件"
}
}示例配置:
json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "/path/to/session_start.py",
"timeout": 30
}
]
}
]
}
}2. SessionEnd - 会话结束
触发时机: 会话终止时
用途:
- 清理临时资源
- 保存会话状态
- 生成会话报告
Matcher 匹配字段: reason
other- 会话结束(目前仅支持此值,包括切换会话、删除会话、清空会话等场景)
输入数据:
json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.txt",
"cwd": "/project/path",
"hook_event_name": "SessionEnd",
"reason": "other"
}输出数据:
json
{
"continue": true,
"systemMessage": "会话已清理,临时文件已删除"
}3. PreToolUse - 工具执行前
触发时机: 任何工具执行前
用途:
- 验证工具参数
- 修改工具输入
- 阻止危险操作
- 权限检查
- 记录审计日志
Matcher 匹配字段: tool_name
- 示例:
execute_command,write_to_file,read_file - 支持正则:
write_to_file|replace_in_file - 匹配所有:
*或空字符串
输入数据:
json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.txt",
"cwd": "/project/path",
"hook_event_name": "PreToolUse",
"tool_name": "execute_command",
"tool_input": {
"command": "npm install",
"requires_approval": false
}
}输出数据 - 允许执行:
json
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow"
}
}输出数据 - 修改参数:
json
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "已添加 --legacy-peer-deps 参数",
"modifiedInput": {
"command": "npm install --legacy-peer-deps",
"requires_approval": false
}
}
}输出数据 - 阻止执行:
json
{
"continue": false,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "检测到危险命令: rm -rf /"
}
}输出数据 - 请求用户确认:
json
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "ask",
"permissionDecisionReason": "检测到 git push --force,是否继续?"
}
}4. PostToolUse - 工具执行后
触发时机: 工具执行完成后
用途:
- 记录工具执行日志
- 后处理工具输出
- 触发后续操作
- 发送通知
Matcher 匹配字段: tool_name
输入数据:
json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.txt",
"cwd": "/project/path",
"hook_event_name": "PostToolUse",
"tool_name": "execute_command",
"tool_input": {
"command": "npm test"
},
"tool_response": {
"exitCode": 0,
"stdout": "All tests passed",
"stderr": ""
}
}输出数据:
json
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "测试已通过,可以继续开发"
}
}5. UserPromptSubmit - 用户输入提交
触发时机: 用户提交消息时
用途:
- 预处理用户输入
- 添加上下文信息
- 检测特定关键词
- 输入验证
Matcher: 不使用(所有提交都触发)
输入数据:
json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.txt",
"cwd": "/project/path",
"hook_event_name": "UserPromptSubmit",
"prompt": "帮我实现一个登录功能"
}输出数据:
json
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "提示:项目已集成 JWT 认证库,建议使用"
}
}阻止输入:
json
{
"continue": false,
"stopReason": "输入包含敏感信息,已阻止"
}6. Stop - Agent 停止响应
触发时机: Agent 完成响应时
用途:
- 提供反馈给 Agent
- 记录执行状态
- 触发后续任务
Matcher: 不使用
输入数据:
json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.txt",
"cwd": "/project/path",
"hook_event_name": "Stop",
"stop_hook_active": false
}输出数据 - 提供反馈 (exit code 2):
json
{
"continue": false,
"stopReason": "请验证代码是否通过了单元测试"
}7. PreCompact - 上下文压缩前
触发时机: 上下文即将被压缩时
用途:
- 保存重要信息
- 提供压缩指导
- 备份完整上下文
Matcher 匹配字段: trigger
manual- 用户手动触发/summarizeauto- 自动压缩
输入数据:
json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.txt",
"cwd": "/project/path",
"hook_event_name": "PreCompact",
"trigger": "auto",
"custom_instructions": "保留所有 API 设计相关的讨论"
}输出数据 (exit code 0):
json
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "PreCompact",
"additionalContext": "重要:保留数据库表结构设计"
}
}说明: Exit code 0 时,stdout 的内容会被添加为额外的压缩指导
Hook 脚本规范
输入格式
Hook 脚本通过 stdin 接收 JSON 格式的输入数据。
通用字段:
json
{
"session_id": "会话 ID",
"transcript_path": "对话记录文件路径",
"cwd": "当前工作目录",
"hook_event_name": "事件名称"
}事件特定字段:
SessionStart:sourceSessionEnd:reasonPreToolUse/PostToolUse:tool_name,tool_input,tool_responseUserPromptSubmit:promptPreCompact:trigger,custom_instructionsStop:stop_hook_active
输出格式
Hook 脚本通过 stdout 返回 JSON 格式的输出。
基本结构:
json
{
"continue": true,
"suppressOutput": false,
"systemMessage": "可选的系统消息",
"stopReason": "阻止原因(当 continue=false 时)",
"hookSpecificOutput": {
"hookEventName": "事件名称",
"permissionDecision": "allow|deny|ask",
"permissionDecisionReason": "决策原因",
"modifiedInput": {},
"additionalContext": "额外上下文"
}
}字段说明:
continue: 是否允许操作继续(false表示阻止)suppressOutput: 是否隐藏 stdout 输出systemMessage: 显示给用户的系统消息stopReason: 阻止原因hookSpecificOutput: 事件特定的输出数据
退出码规范
| 退出码 | 含义 | 行为 |
|---|---|---|
| 0 | 成功执行 | 允许操作继续,stdout 可能被处理 |
| 1 | 非阻塞错误 | 显示 stderr 作为警告,允许继续 |
| 2 | 阻塞错误 | 阻止操作,stderr 传递给 Agent/模型 |
| 其他 | 非阻塞错误 | 同退出码 1 |
特殊规则:
- PreToolUse: 退出码 2 会阻止工具执行
- Stop: 退出码 2 表示提供反馈,stderr 会注入到下一条消息
- PreCompact: 退出码 0 时,stdout 会作为额外的压缩指导
环境变量
Hook 脚本执行时可访问以下环境变量:
CLAUDE_PROJECT_DIR: 项目根目录(兼容 Claude Code)CODEBUDDY_PROJECT_DIR: 项目根目录(CodeBuddy 特定)
配置说明
配置文件位置
优先级(高到低):
- 项目级:
<workspace>/.codebuddy/settings.json - 用户级:
~/.codebuddy/settings.json
项目级配置会覆盖用户级配置。
配置文件结构
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "execute_command",
"hooks": [
{
"type": "command",
"command": "/absolute/path/to/script.py",
"timeout": 10
}
]
},
{
"matcher": "write_to_file|replace_in_file",
"hooks": [
{
"type": "command",
"command": "/path/to/backup_script.sh",
"timeout": 20
}
]
}
],
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "/path/to/init.py",
"timeout": 30
}
]
}
]
}
}配置字段说明
matcher (匹配器)
正则表达式,用于匹配特定条件。
语法:
- 空字符串
""或"*": 匹配所有 - 单个值:
"execute_command" - 多个值:
"write_to_file|replace_in_file" - 正则表达式:
"read.*|search.*"
不同事件的匹配目标:
PreToolUse/PostToolUse: 匹配tool_nameSessionStart: 匹配sourceSessionEnd: 匹配reasonPreCompact: 匹配triggerUserPromptSubmit/Stop: 不使用 matcher
command (命令路径)
Hook 脚本的路径。
要求:
- ✅ 推荐使用绝对路径
- ✅ 支持环境变量:
"$CODEBUDDY_PROJECT_DIR/.codebuddy/hooks/script.py" - ✅ 可包含解释器:
"python3 /path/to/script.py" - ⚠️ 需要确保脚本有执行权限
timeout (超时时间)
Hook 执行的超时时间,单位:秒。
- 默认值:
60秒 - 推荐设置: 根据脚本复杂度调整
- 简单验证: 5-10 秒
- 文件操作: 15-30 秒
- 网络请求: 30-60 秒
完整示例
示例 1: 命令安全验证
场景: 阻止危险的 rm -rf 命令
Hook 脚本 (validate_command.py):
python
#!/usr/bin/env python3
import json
import sys
DANGEROUS_COMMANDS = ['rm -rf /', 'dd if=/dev/zero', 'mkfs']
def main():
input_data = json.loads(sys.stdin.read())
if input_data.get('tool_name') != 'execute_command':
print(json.dumps({"continue": True}))
return 0
command = input_data.get('tool_input', {}).get('command', '')
for dangerous in DANGEROUS_COMMANDS:
if dangerous in command:
output = {
"continue": False,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"检测到危险命令: {dangerous}"
}
}
print(json.dumps(output, ensure_ascii=False))
return 0
print(json.dumps({"continue": True}))
return 0
if __name__ == "__main__":
sys.exit(main())配置:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "execute_command",
"hooks": [
{
"type": "command",
"command": "/path/to/validate_command.py",
"timeout": 10
}
]
}
]
}
}示例 2: 智能修改命令参数
场景: 自动为 npm install 添加 --legacy-peer-deps 参数
Hook 脚本 (modify_npm.py):
python
#!/usr/bin/env python3
import json
import sys
import re
def main():
input_data = json.loads(sys.stdin.read())
if input_data.get('tool_name') != 'execute_command':
print(json.dumps({"continue": True}))
return 0
tool_input = input_data.get('tool_input', {})
command = tool_input.get('command', '')
# 检查是否是 npm install
if re.match(r'^npm\s+(i|install)\b', command.strip()):
# 如果没有 --legacy-peer-deps,添加它
if '--legacy-peer-deps' not in command:
modified_command = command.strip() + ' --legacy-peer-deps'
output = {
"continue": True,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "已自动添加 --legacy-peer-deps 参数",
"modifiedInput": {
"command": modified_command,
"requires_approval": tool_input.get('requires_approval', False)
}
}
}
print(json.dumps(output, ensure_ascii=False))
return 0
print(json.dumps({"continue": True}))
return 0
if __name__ == "__main__":
sys.exit(main())示例 3: 文件修改前自动备份
场景: 在修改文件前自动创建备份
Hook 脚本 (backup_files.py):
python
#!/usr/bin/env python3
import json
import sys
import os
import shutil
from datetime import datetime
def main():
input_data = json.loads(sys.stdin.read())
tool_name = input_data.get('tool_name', '')
# 只处理文件写入工具
if tool_name not in ['write_to_file', 'replace_in_file']:
print(json.dumps({"continue": True}))
return 0
tool_input = input_data.get('tool_input', {})
file_path = tool_input.get('filePath')
if not file_path or not os.path.exists(file_path):
print(json.dumps({"continue": True}))
return 0
# 创建备份目录
project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')
backup_dir = os.path.join(project_dir, '.codebuddy', 'backups')
os.makedirs(backup_dir, exist_ok=True)
# 生成备份文件名
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_name = f"{os.path.basename(file_path)}.{timestamp}.bak"
backup_path = os.path.join(backup_dir, backup_name)
# 创建备份
shutil.copy2(file_path, backup_path)
output = {
"continue": True,
"systemMessage": f"已备份至: {backup_path}"
}
print(json.dumps(output, ensure_ascii=False))
return 0
if __name__ == "__main__":
sys.exit(main())配置:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "write_to_file|replace_in_file",
"hooks": [
{
"type": "command",
"command": "/path/to/backup_files.py",
"timeout": 15
}
]
}
]
}
}示例 4: 会话启动时注入项目上下文
场景: 在会话开始时自动注入项目配置信息
Hook 脚本 (session_start.py):
python
#!/usr/bin/env python3
import json
import sys
import os
def main():
input_data = json.loads(sys.stdin.read())
project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')
# 读取项目配置
config_file = os.path.join(project_dir, '.codebuddy', 'project.json')
project_info = ""
if os.path.exists(config_file):
with open(config_file, 'r') as f:
config = json.load(f)
project_info = f"""
项目名称: {config.get('name', 'Unknown')}
技术栈: {', '.join(config.get('tech_stack', []))}
编码规范: {config.get('coding_standard', 'Standard')}
"""
output = {
"continue": True,
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": f"""
会话已启动!
项目目录: {project_dir}
启动源: {input_data.get('source', 'unknown')}
{project_info}
"""
}
}
print(json.dumps(output, ensure_ascii=False))
return 0
if __name__ == "__main__":
sys.exit(main())示例 5: 上下文压缩前保存重要信息
场景: 在自动压缩前保存完整对话历史
Hook 脚本 (save_context.py):
python
#!/usr/bin/env python3
import json
import sys
import os
import shutil
from datetime import datetime
def main():
input_data = json.loads(sys.stdin.read())
# 只处理自动压缩
if input_data.get('trigger') != 'auto':
print(json.dumps({"continue": True}))
return 0
project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')
transcript_path = input_data.get('transcript_path', '')
if not transcript_path or not os.path.exists(transcript_path):
print(json.dumps({"continue": True}))
return 0
# 创建保存目录
save_dir = os.path.join(project_dir, '.codebuddy', 'context_history')
os.makedirs(save_dir, exist_ok=True)
# 保存对话历史
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
save_path = os.path.join(save_dir, f'transcript_{timestamp}.txt')
shutil.copy2(transcript_path, save_path)
output = {
"continue": True,
"systemMessage": f"上下文已保存至: {save_path}"
}
print(json.dumps(output, ensure_ascii=False))
return 0
if __name__ == "__main__":
sys.exit(main())配置:
json
{
"hooks": {
"PreCompact": [
{
"matcher": "auto",
"hooks": [
{
"type": "command",
"command": "/path/to/save_context.py",
"timeout": 20
}
]
}
]
}
}实战指南
快速开始 - 5 分钟配置第一个 Hook
第一步:创建配置文件
bash
mkdir -p ~/.codebuddy
cat > ~/.codebuddy/settings.json << 'EOF'
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "/usr/bin/env python3 -c \"import json,sys; print(json.dumps({'continue': True, 'hookSpecificOutput': {'hookEventName': 'SessionStart', 'additionalContext': 'Hook 配置成功!'}}))\"",
"timeout": 5
}
]
}
]
}
}
EOF第二步:重启 Agent
启动新会话,如果看到 "Hook 配置成功!" 说明配置生效。
第三步:创建你的第一个真实 Hook
bash
# 创建 Hook 脚本目录
mkdir -p ~/.codebuddy/hooks
# 创建测试脚本
cat > ~/.codebuddy/hooks/my_first_hook.py << 'EOF'
#!/usr/bin/env python3
import json
import sys
def main():
input_data = json.loads(sys.stdin.read())
output = {
"continue": True,
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": f"欢迎使用 Agent-Craft!当前项目: {input_data.get('cwd', 'unknown')}"
}
}
print(json.dumps(output, ensure_ascii=False))
return 0
if __name__ == "__main__":
sys.exit(main())
EOF
# 添加执行权限
chmod +x ~/.codebuddy/hooks/my_first_hook.py第四步:更新配置文件
json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "/Users/YOUR_USERNAME/.codebuddy/hooks/my_first_hook.py",
"timeout": 10
}
]
}
]
}
}调试技巧进阶
技巧1: 使用日志文件调试
python
import sys
def debug_log(message):
"""写入调试日志而不影响 stdout"""
with open('/tmp/hook_debug.log', 'a') as f:
f.write(f"{message}\n")
# 在 Hook 脚本中使用
debug_log(f"Received input: {json.dumps(input_data)}")技巧2: 验证 JSON 输出格式
bash
# 测试脚本输出的 JSON 是否有效
echo '{"hook_event_name":"SessionStart"}' | python3 your_hook.py | jq .技巧3: 监控 Hook 执行
bash
# 实时查看 Hook 日志
tail -f ~/.codebuddy/logs/agent-craft.log | grep -i hook技巧4: 使用环境变量传递信息
python
import os
# 在 Hook 中获取项目目录
project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')
claude_dir = os.environ.get('CLAUDE_PROJECT_DIR', '') # 兼容 Claude Code常见 Hook 模式
模式1: 白名单验证
python
ALLOWED_COMMANDS = [
'npm install',
'npm test',
'git status',
'git diff'
]
def is_allowed(command):
return any(command.startswith(allowed) for allowed in ALLOWED_COMMANDS)模式2: 参数增强
python
def enhance_command(command):
"""自动添加常用参数"""
enhancements = {
'npm install': ' --legacy-peer-deps',
'git push': ' --dry-run', # 安全模式
}
for prefix, suffix in enhancements.items():
if command.startswith(prefix) and suffix not in command:
return command + suffix
return command模式3: 条件路由
python
def should_block(input_data):
"""根据多个条件判断是否阻止"""
tool_name = input_data.get('tool_name')
tool_input = input_data.get('tool_input', {})
# 规则1: 阻止删除重要文件
if tool_name == 'delete_files':
file_path = tool_input.get('target_file', '')
if any(important in file_path for important in ['.git', 'package.json']):
return True, "不能删除重要文件"
# 规则2: 阻止危险命令
if tool_name == 'execute_command':
command = tool_input.get('command', '')
if 'rm -rf /' in command or 'dd if=' in command:
return True, "检测到危险命令"
return False, None项目模板推荐
Node.js 项目 Hook 配置
json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "node ~/.codebuddy/hooks/nodejs-init.js",
"timeout": 15
}
]
}
],
"PreToolUse": [
{
"matcher": "execute_command",
"hooks": [
{
"type": "command",
"command": "python3 ~/.codebuddy/hooks/npm-safety-check.py",
"timeout": 5
}
]
}
]
}
}Python 项目 Hook 配置
json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "python3 ~/.codebuddy/hooks/python-env-check.py",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "write_to_file|replace_in_file",
"hooks": [
{
"type": "command",
"command": "python3 ~/.codebuddy/hooks/python-lint.py",
"timeout": 20
}
]
}
]
}
}最佳实践
1. 脚本开发
- ✅ 使用绝对路径: 将 Hook 脚本放在固定目录,使用绝对路径引用
- ✅ 错误处理: 确保脚本有完善的异常处理,避免阻塞主流程
- ✅ 快速执行: Hook 应快速完成,避免耗时操作
- ✅ 幂等性: Hook 可能被多次调用,确保多次执行结果一致
- ✅ 日志记录: 使用
sys.stderr输出调试信息,不要污染 stdout
2. 安全性
- ⚠️ 输入验证: 始终验证输入数据的合法性
- ⚠️ 白名单优于黑名单: 使用白名单机制进行权限控制
- ⚠️ 避免代码注入: 不要直接执行用户输入
- ⚠️ 最小权限: Hook 脚本应以最小必要权限运行
3. 性能优化
- ⏱ 设置合理超时: 根据脚本复杂度设置 timeout
- ⏱ 并行设计: 避免 Hook 之间的依赖,充分利用并行执行
- ⏱ 缓存结果: 对于重复计算,考虑缓存结果
4. 调试技巧
手动测试 Hook 脚本:
bash
echo '{"hook_event_name":"PreToolUse","tool_name":"execute_command","tool_input":{"command":"npm install"}}' | \
python3 /path/to/your_hook.py调试输出:
python
# 在 Hook 脚本中输出调试信息
import sys
sys.stderr.write(f"[DEBUG] Processing command: {command}\n")
sys.stderr.flush()5. 配置管理
- 📁 项目特定 Hook: 放在
<workspace>/.codebuddy/下,随项目版本控制 - 📁 个人 Hook: 放在
~/.codebuddy/下,跨项目复用
性能优化建议
1. 减少 Hook 执行时间
- ✅ 使用快速语言: Shell 脚本通常比 Python 启动更快
- ✅ 避免重复工作: 缓存计算结果
- ✅ 异步处理: 非关键操作使用后台任务
- ✅ 提前退出: 尽早判断是否需要处理
示例:
python
# 不好的做法:每次都加载大文件
def main():
with open('huge_config.json', 'r') as f:
config = json.load(f) # 每次都读取
# ... 处理逻辑
# 好的做法:缓存配置
CONFIG_CACHE = None
def get_config():
global CONFIG_CACHE
if CONFIG_CACHE is None:
with open('huge_config.json', 'r') as f:
CONFIG_CACHE = json.load(f)
return CONFIG_CACHE2. 优化 Matcher 配置
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "execute_command",
"hooks": [
{
"type": "command",
"command": "/path/to/fast_check.sh",
"timeout": 3
}
]
},
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "/path/to/general_check.py",
"timeout": 10
}
]
}
]
}
}3. 并行执行的注意事项
- 多个 Hook 会并行执行,不要依赖执行顺序
- 避免 Hook 之间的文件写入冲突
- 使用文件锁或原子操作处理共享资源
python
import fcntl
def safe_append_log(message):
"""线程安全的日志写入"""
with open('/tmp/hook.log', 'a') as f:
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
f.write(message + '\n')
fcntl.flock(f.fileno(), fcntl.LOCK_UN)安全最佳实践
1. 输入验证
永远不要信任输入数据:
python
def validate_input(input_data):
"""验证输入数据的完整性"""
required_fields = ['hook_event_name', 'session_id']
for field in required_fields:
if field not in input_data:
raise ValueError(f"Missing required field: {field}")
# 验证字段类型
if not isinstance(input_data.get('tool_input'), dict):
raise ValueError("tool_input must be a dictionary")
return True2. 命令注入防护
不要直接执行用户输入:
python
# ❌ 危险:直接执行
os.system(f"echo {user_input}")
# ✅ 安全:使用参数化
import subprocess
subprocess.run(['echo', user_input], check=True)3. 路径遍历防护
python
import os
def safe_file_access(file_path, project_dir):
"""确保文件路径在项目目录内"""
abs_path = os.path.abspath(file_path)
abs_project = os.path.abspath(project_dir)
if not abs_path.startswith(abs_project):
raise ValueError("Path traversal detected")
return abs_path4. 权限最小化
python
# Hook 脚本应该以最小权限运行
# 避免使用 sudo 或 root 权限
# ✅ 检查权限
if os.geteuid() == 0:
print("Warning: Running as root is not recommended", file=sys.stderr)高级用法
条件执行
在 Hook 脚本中根据条件决定是否处理:
python
def main():
input_data = json.loads(sys.stdin.read())
# 仅在特定条件下处理
if not should_process(input_data):
print(json.dumps({"continue": True}))
return 0
# 执行处理逻辑
...多规则组合
在一个 Hook 脚本中实现多个规则:
python
def main():
input_data = json.loads(sys.stdin.read())
# 应用多个规则
for rule in RULES:
if rule.matches(input_data):
return rule.apply(input_data)
# 默认行为
print(json.dumps({"continue": True}))
return 0外部服务集成
Hook 可以调用外部 API 或服务:
python
import requests
def check_with_external_service(command):
response = requests.post('https://api.example.com/validate',
json={'command': command},
timeout=5)
return response.json()常见问题
Q1: Hook 没有被执行?
检查清单:
- ✅ 配置文件路径正确 (
settings.json在.codebuddy目录下) - ✅
hooks字段配置正确,JSON 格式有效 - ✅
matcher正则表达式能匹配目标 - ✅ Hook 脚本有执行权限 (
chmod +x script.py) - ✅ 脚本路径正确(推荐使用绝对路径)
- ✅ 脚本第一行有正确的 shebang (
#!/usr/bin/env python3)
Q2: Hook 执行超时?
解决方法:
- 增加
timeout配置值 - 优化 Hook 脚本性能
- 检查是否有死循环或阻塞操作
Q3: 如何调试 Hook 脚本?
调试步骤:
- 使用
echo手动传入测试数据 - 在脚本中使用
sys.stderr输出调试信息 - 验证 JSON 格式是否正确
Q4: 多个 Hook 的执行顺序?
答案:
- Hook 并行执行,不保证执行顺序
- 如需顺序执行,将逻辑合并到一个 Hook 脚本中
- 相同命令会自动去重
Q5: Hook 修改的参数不生效?
检查要点:
- 确保返回了
modifiedInput字段 - 确保
permissionDecision为allow - 检查字段名称是否与工具参数匹配
- 验证 JSON 格式正确
- 确保
continue为true
Q6: SessionStart Hook 每次请求都触发?
原因: SessionStart 应该只在新会话触发一次
解决方法:
- 系统通过
conversationId跟踪会话 - 同一会话中多次请求不会重复触发
- 如果仍有问题,查看日志中的会话 ID 是否变化
附录
A. 完整的 HookInput 接口定义
typescript
interface HookInput {
// 通用字段
session_id?: string; // 会话 ID
transcript_path?: string; // 对话记录路径
cwd?: string; // 当前工作目录
hook_event_name: string; // Hook 事件名称
// SessionStart 专用(目前仅支持 'startup')
source?: 'startup';
// UserPromptSubmit 专用
prompt?: string; // 用户输入内容
// PreToolUse/PostToolUse 专用
tool_name?: string; // 工具名称
tool_input?: Record<string, any>; // 工具输入参数
tool_response?: any; // 工具响应(仅 PostToolUse)
// Stop 专用
stop_hook_active?: boolean; // 是否已激活 Stop Hook
// PreCompact 专用
trigger?: 'manual' | 'auto'; // 触发方式
custom_instructions?: string; // 自定义压缩指令
}B. 完整的 HookOutput 接口定义
typescript
interface HookOutput {
// 基本控制
continue?: boolean; // 是否继续执行(默认 true)
stopReason?: string; // 停止原因
suppressOutput?: boolean; // 是否隐藏输出
systemMessage?: string; // 系统消息
// Hook 特定输出
hookSpecificOutput?: {
hookEventName: string; // Hook 事件名称
// PreToolUse 专用
permissionDecision?: 'allow' | 'deny' | 'ask';
permissionDecisionReason?: string;
modifiedInput?: Record<string, any>;
// SessionStart/UserPromptSubmit/PostToolUse 专用
additionalContext?: string; // 额外上下文
};
}C. 环境变量列表
| 环境变量 | 说明 | 示例值 |
|---|---|---|
CODEBUDDY_PROJECT_DIR | 项目根目录 | /path/to/project |
CLAUDE_PROJECT_DIR | 项目根目录(Claude Code 兼容) | /path/to/project |
D. 退出码详细说明
| 退出码 | 含义 | stdout | stderr | 行为 |
|---|---|---|---|---|
| 0 | 成功 | 作为结果处理 | 忽略 | 继续执行,可能注入上下文 |
| 1 | 警告 | 忽略 | 作为警告显示 | 继续执行 |
| 2 | 阻止/反馈 | 忽略 | 传递给 Agent | PreToolUse: 阻止执行<br>Stop: 提供反馈 |
| 其他 | 错误 | 忽略 | 作为警告显示 | 继续执行 |
E. 常用工具名称列表
文件操作工具:
read_file- 读取文件write_to_file- 写入文件replace_in_file- 替换文件内容delete_files- 删除文件list_files- 列出文件search_file- 搜索文件
代码操作工具:
search_content- 搜索内容read_lints- 读取 Lint 错误
执行工具:
execute_command- 执行命令preview_url- 预览 URL
其他工具:
task- 创建子任务web_search- 网络搜索web_fetch- 获取网页内容ask_followup_question- 询问用户
总结
Hook 功能提供了强大的扩展能力,允许您在 AI Agent 的关键节点插入自定义逻辑。通过合理使用 Hook,您可以:
- 🛡️ 增强安全性: 验证和阻止危险操作
- 🔧 自动化流程: 智能修改参数、自动备份文件
- 📊 监控审计: 记录工具执行日志
- 🎯 定制行为: 注入项目特定上下文
开始使用 Hook 功能,让您的 AI Agent 更智能、更安全、更符合项目需求!
Happy Hooking! 🎣