本教程深入剖析Claude Code Hooks系统的完整事件模型、JSON输入输出协议、退出码语义和Matcher过滤机制。通过12个生命周期事件的逐一拆解和6个生产级实战案例,帮助开发者构建安全护栏、自动格式化、上下文注入、会话管理等能力。适合已具备Claude Code基础使用经验、希望将开发流程自动化到极致的中高级开发者。
Claude Code的Hooks系统本质上是一个事件驱动的中间件层。每当Claude Code执行工具调用、用户提交prompt、会话启动或结束时,都会触发预注册的hook脚本。这些脚本可以拦截、修改、阻止或记录每一个操作。

图表 1
Claude Code支持13个hook事件,分为三大类:
| 类别 | 事件 | 触发时机 | Matcher支持 |
|---|---|---|---|
| 工具生命周期 | PreToolUse |
工具执行前 | 正则匹配tool_name |
PostToolUse |
工具执行后 | 正则匹配tool_name | |
PostToolUseFailure |
工具执行失败后 | 正则匹配tool_name | |
| 会话生命周期 | SessionStart |
会话启动/恢复 | 不支持 |
SessionEnd |
会话结束 | 不支持 | |
Setup |
首次配置 | 不支持 | |
Stop |
主Agent完成回复 | 不支持 | |
SubagentStart |
子Agent启动 | 不支持 | |
SubagentStop |
子Agent完成 | 不支持 | |
| 用户交互 | UserPromptSubmit |
用户提交prompt | 不支持 |
Notification |
系统通知(权限请求等) | 不支持 | |
PermissionRequest |
权限请求 | 不支持 | |
| 上下文管理 | PreCompact |
对话压缩前 | 不支持 |
Hooks可以配置在三个层级,后加载的覆盖先加载的:
~/.claude/settings.json # 全局配置,所有项目生效
项目/.claude/settings.json # 项目级配置,团队共享
项目/.claude/settings.local.json # 本地配置,不提交到Git
每个hook脚本通过stdin接收JSON,通过stdout返回JSON,通过退出码传达决策。这是整个系统最核心的通信协议。
所有hook事件共享的基础输入结构:
{
"session_id": "abc123-def456",
"transcript_path": "/tmp/claude-session-abc123.jsonl",
"cwd": "/home/user/my-project",
"hook_event_name": "PreToolUse"
}
退出码是hook控制Claude Code行为的核心手段:
| 退出码 | 含义 | stdout行为 | stderr行为 |
|---|---|---|---|
| 0 | 成功/放行 | 对UserPromptSubmit和SessionStart注入上下文,其他事件仅记录 |
忽略 |
| 2 | 阻止/拦截 | 忽略 | 路由到Claude或用户(见下表) |
| 其他 | 非阻塞错误 | 忽略 | 显示给用户 |

图表 2
关键区别:stderr路由目标是Claude还是用户决定了你的错误消息应该写给谁看。当stderr发给Claude时,Claude会根据消息内容调整后续行为;发给用户时,仅作为显示信息。
PreToolUse是使用频率最高的hook事件,它在Claude准备执行工具但还未执行时触发。你可以用它来阻止危险命令、保护敏感文件、执行预检查。
{
"session_id": "abc123",
"transcript_path": "/tmp/session.jsonl",
"cwd": "/home/user/project",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules",
"description": "Clean node_modules"
}
}
推荐方式(hookSpecificOutput):
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "不允许删除node_modules,请使用 npm ci 重建"
}
}
三种决策值:
- "allow" — 绕过权限UI,直接执行(原因显示给用户)
- "deny" — 阻止调用(原因显示给Claude,Claude会据此调整)
- "ask" — 弹出权限确认UI(原因显示给用户)
Legacy方式(仍然支持):
{
"decision": "block",
"reason": "危险命令已被拦截"
}
只有PreToolUse和PostToolUse支持matcher字段,它是一个正则表达式,匹配tool_name:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/check_bash.py"
}
]
},
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/check_file_write.py"
}
]
}
]
}
}
对于MCP工具,名称格式为mcp__<server>__<tool>,例如:
{
"matcher": "mcp__filesystem__write_file",
"hooks": [...]
}
空字符串""匹配所有工具。
创建文件 .claude/hooks/pre_tool_use.py:
#!/usr/bin/env python3
"""PreToolUse Hook: 拦截危险的Bash命令和敏感文件访问"""
import json
import sys
import re
from pathlib import Path
def is_dangerous_rm(command: str) -> bool:
"""检测危险的rm命令模式"""
normalized = ' '.join(command.lower().split())
patterns = [
r'\brm\s+.*-[a-z]*r[a-z]*f', # rm -rf, rm -fr
r'\brm\s+--recursive\s+--force', # 长选项格式
r'\brm\s+-r\s+.*-f', # 分开的选项
]
for pattern in patterns:
if re.search(pattern, normalized):
return True
# rm -r 针对危险路径
dangerous_paths = ['/', '/*', '~', '~/', '$HOME', '*', '.']
if re.search(r'\brm\s+.*-[a-z]*r', normalized):
for path in dangerous_paths:
if re.search(re.escape(path), normalized):
return True
return False
def is_protected_file(tool_name: str, tool_input: dict) -> bool:
"""检查是否访问受保护的文件"""
protected = ['.env', '.env.production', '.env.staging']
if tool_name in ('Read', 'Edit', 'Write', 'MultiEdit'):
file_path = tool_input.get('file_path', '')
for p in protected:
if p in file_path and not file_path.endswith('.env.sample'):
return True
return False
def main():
try:
data = json.load(sys.stdin)
tool_name = data.get('tool_name', '')
tool_input = data.get('tool_input', {})
# 检查受保护文件
if is_protected_file(tool_name, tool_input):
msg = json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason":
"受保护的.env文件不可修改,请使用.env.sample作为模板"
}
})
print(msg)
sys.exit(0)
# 检查危险Bash命令
if tool_name == 'Bash':
command = tool_input.get('command', '')
if is_dangerous_rm(command):
msg = json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason":
f"检测到危险删除命令: '{command[:80]}'。已被安全策略拦截。"
}
})
print(msg)
sys.exit(0)
sys.exit(0) # 放行
except Exception:
sys.exit(0) # 异常时放行,不阻塞工作流
if __name__ == '__main__':
main()
在.claude/settings.json中注册:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/pre_tool_use.py"
}
]
}
]
}
}
预期效果: 当Claude尝试执行rm -rf /或编辑.env文件时,命令会被阻止,Claude会收到原因说明并调整行为。
PostToolUse在工具成功执行后触发。它无法阻止已发生的操作,但可以:
- 对工具结果给出自动反馈
- 触发后续自动化(格式化、lint、测试)
- 记录审计日志
{
"session_id": "abc123",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/home/user/project/src/app.tsx",
"content": "export default function App() { return <div>Hello</div> }"
},
"tool_response": {
"success": true
}
}
#!/usr/bin/env python3
"""PostToolUse Hook: 自动格式化编辑过的文件"""
import json
import sys
import subprocess
from pathlib import Path
# 需要自动格式化的文件扩展名和对应命令
FORMATTERS = {
'.ts': 'npx prettier --write {path}',
'.tsx': 'npx prettier --write {path}',
'.js': 'npx prettier --write {path}',
'.jsx': 'npx prettier --write {path}',
'.css': 'npx prettier --write {path}',
'.json': 'npx prettier --write {path}',
'.py': 'python3 -m black {path}',
'.go': 'gofmt -w {path}',
'.rs': 'rustfmt {path}',
}
def should_format(tool_name: str, tool_input: dict) -> str | None:
"""判断是否需要格式化,返回文件路径或None"""
if tool_name not in ('Write', 'Edit', 'MultiEdit'):
return None
file_path = tool_input.get('file_path', '')
suffix = Path(file_path).suffix
if suffix in FORMATTERS:
return file_path
return None
def main():
try:
data = json.load(sys.stdin)
tool_name = data.get('tool_name', '')
tool_input = data.get('tool_input', {})
file_path = should_format(tool_name, tool_input)
if not file_path:
sys.exit(0)
suffix = Path(file_path).suffix
cmd = FORMATTERS[suffix].format(path=file_path)
result = subprocess.run(
cmd, shell=True, capture_output=True, text=True, timeout=30
)
if result.returncode == 0:
# 格式化成功,给Claude一个反馈
output = json.dumps({
"decision": None,
"reason": f"已自动格式化 {file_path}"
})
print(output)
else:
# 格式化失败,让Claude知道
output = json.dumps({
"decision": "block",
"reason": f"格式化失败: {result.stderr[:200]}"
})
print(output)
sys.exit(0)
except Exception as e:
print(f"格式化hook异常: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
配置Matcher精确匹配文件修改工具:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/auto_format.py"
}
]
}
]
}
}
UserPromptSubmit在用户按下Enter提交prompt之前触发。它有两个独特能力:
1. 阻止prompt:可以拦截不合适的输入
2. 注入上下文:通过stdout向Claude的上下文中追加额外信息
{
"session_id": "abc123",
"hook_event_name": "UserPromptSubmit",
"prompt": "帮我删除所有node_modules文件夹"
}
这是UserPromptSubmit最强大的特性。当hook以exit code 0退出时,stdout内容会被注入到Claude的上下文中,相当于用户在prompt后面附加了额外信息,但用户自己看不到。
#!/usr/bin/env python3
"""UserPromptSubmit Hook: 自动注入当前分支和最近改动信息"""
import json
import sys
import subprocess
def get_git_context() -> str:
"""获取Git仓库当前状态"""
parts = []
# 当前分支
result = subprocess.run(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
branch = result.stdout.strip()
parts.append(f"当前Git分支: {branch}")
# 未提交的改动数量
result = subprocess.run(
['git', 'status', '--porcelain'],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
changes = [l for l in result.stdout.strip().split('\n') if l]
if changes:
parts.append(f"未提交的文件改动: {len(changes)}个")
# 列出前5个改动文件
for change in changes[:5]:
parts.append(f" {change}")
# 最近3条commit
result = subprocess.run(
['git', 'log', '--oneline', '-3'],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0 and result.stdout.strip():
parts.append("最近提交:")
for line in result.stdout.strip().split('\n'):
parts.append(f" {line}")
return '\n'.join(parts) if parts else ""
def main():
try:
data = json.load(sys.stdin)
prompt = data.get('prompt', '')
# 检测到关键词时注入额外上下文
keywords = ['提交', 'commit', '发布', 'deploy', 'push', '测试', 'test']
should_inject = any(kw in prompt.lower() for kw in keywords)
if should_inject:
context = get_git_context()
if context:
# stdout内容会被注入到上下文
print(f"\n[自动上下文]\n{context}\n")
sys.exit(0)
except Exception:
sys.exit(0)
if __name__ == '__main__':
main()
SessionStart在每次启动或恢复Claude Code会话时触发。与UserPromptSubmit类似,它的stdout(exit code 0时)会注入到上下文中,这使它成为会话引导的绝佳工具。
{
"session_id": "abc123",
"hook_event_name": "SessionStart",
"source": "startup", // "startup" | "resume" | "clear"
"cwd": "/home/user/project"
}
#!/usr/bin/env python3
"""SessionStart Hook: 根据项目结构自动注入引导信息"""
import json
import sys
from pathlib import Path
def detect_project_type(cwd: str) -> dict:
"""检测项目类型和关键信息"""
root = Path(cwd)
info = {"type": "unknown", "tips": []}
# Node.js / React 项目
if (root / 'package.json').exists():
info["type"] = "node"
pkg = json.loads((root / 'package.json').read_text())
info["name"] = pkg.get('name', 'unknown')
scripts = pkg.get('scripts', {})
if 'test' in scripts:
info["tips"].append(f"测试命令: npm test ({scripts['test']})")
if 'build' in scripts:
info["tips"].append(f"构建命令: npm run build ({scripts['build']})")
if 'lint' in scripts:
info["tips"].append(f"代码检查: npm run lint ({scripts['lint']})")
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
if 'react' in deps:
info["framework"] = f"React {deps['react']}"
if 'next' in deps:
info["framework"] = f"Next.js {deps['next']}"
if 'vue' in deps:
info["framework"] = f"Vue {deps['vue']}"
# Python 项目
elif (root / 'pyproject.toml').exists() or (root / 'setup.py').exists():
info["type"] = "python"
if (root / 'pyproject.toml').exists():
info["tips"].append("使用 uv 管理依赖")
if (root / 'pytest.ini').exists() or (root / 'conftest.py').exists():
info["tips"].append("测试命令: pytest")
# Go 项目
elif (root / 'go.mod').exists():
info["type"] = "go"
info["tips"].extend([
"构建: go build ./...",
"测试: go test ./...",
"格式化: gofmt -w ."
])
# Rust 项目
elif (root / 'Cargo.toml').exists():
info["type"] = "rust"
info["tips"].extend([
"构建: cargo build",
"测试: cargo test",
"格式化: cargo fmt"
])
# 检测CLAUDE.md
if (root / 'CLAUDE.md').exists():
info["tips"].append("已检测到CLAUDE.md项目配置文件")
return info
def main():
try:
data = json.load(sys.stdin)
cwd = data.get('cwd', '.')
source = data.get('source', 'startup')
if source == 'startup':
info = detect_project_type(cwd)
lines = ["[项目自动识别]"]
lines.append(f"项目类型: {info['type']}")
if 'framework' in info:
lines.append(f"框架: {info['framework']}")
if 'name' in info:
lines.append(f"项目名: {info['name']}")
if info['tips']:
lines.append("可用命令:")
for tip in info['tips']:
lines.append(f" - {tip}")
print('\n'.join(lines))
sys.exit(0)
except Exception:
sys.exit(0)
if __name__ == '__main__':
main()
效果: 每次启动Claude Code时,Claude会自动"看到"项目的技术栈和可用命令,无需手动在CLAUDE.md中重复配置。
Stop事件在主Agent完成回复时触发,SubagentStop在子Agent(通过Task工具派发)完成时触发。通过返回exit code 2或decision: "block",你可以阻止Agent停止,让它继续工作。
{
"session_id": "abc123",
"hook_event_name": "Stop",
"stop_hook_active": false
}
#!/usr/bin/env python3
"""Stop Hook: 确保所有改动通过lint和测试后才允许结束"""
import json
import sys
import subprocess
from pathlib import Path
def has_uncommitted_changes() -> bool:
result = subprocess.run(
['git', 'diff', '--exit-code'],
capture_output=True, timeout=10
)
return result.returncode != 0
def run_lint() -> tuple[bool, str]:
"""运行lint检查"""
root = Path.cwd()
if (root / 'package.json').exists():
result = subprocess.run(
['npx', 'eslint', '.', '--max-warnings=0'],
capture_output=True, text=True, timeout=60
)
if result.returncode != 0:
return False, result.stdout + result.stderr
if (root / 'pyproject.toml').exists():
result = subprocess.run(
['python3', '-m', 'ruff', 'check', '.'],
capture_output=True, text=True, timeout=60
)
if result.returncode != 0:
return False, result.stdout + result.stderr
return True, ""
def run_tests() -> tuple[bool, str]:
"""运行测试"""
root = Path.cwd()
if (root / 'package.json').exists():
result = subprocess.run(
['npm', 'test'], capture_output=True, text=True, timeout=120
)
if result.returncode != 0:
return False, result.stdout[-500:] # 最后500字符
return True, ""
def main():
try:
data = json.load(sys.stdin)
if not has_uncommitted_changes():
sys.exit(0) # 没有改动,直接放行
# 有未提交改动,执行检查
lint_ok, lint_err = run_lint()
if not lint_ok:
output = json.dumps({
"decision": "block",
"reason": f"代码Lint检查未通过,请修复后再完成:\n{lint_err[:500]}"
})
print(output)
sys.exit(0)
test_ok, test_err = run_tests()
if not test_ok:
output = json.dumps({
"decision": "block",
"reason": f"测试未通过,请修复后再完成:\n{test_err[:500]}"
})
print(output)
sys.exit(0)
sys.exit(0) # 全部通过
except Exception:
sys.exit(0)
if __name__ == '__main__':
main()
在系统通知时触发(如等待权限确认、长时间无输入等)。一个常见用途是语音提醒或桌面通知。
#!/usr/bin/env python3
"""Notification Hook: 发送桌面通知"""
import json
import sys
import subprocess
import os
def send_desktop_notification(message: str):
"""发送桌面通知(Linux/macOS)"""
if sys.platform == 'darwin':
# macOS: 使用osascript
script = f'display notification "{message}" with title "Claude Code"'
subprocess.run(['osascript', '-e', script], timeout=5)
else:
# Linux: 使用notify-send
subprocess.run(
['notify-send', 'Claude Code', message, '-t', '5000'],
timeout=5
)
def main():
try:
data = json.load(sys.stdin)
message = data.get('message', '需要你的关注')
send_desktop_notification(message)
sys.exit(0)
except Exception:
sys.exit(0)
if __name__ == '__main__':
main()
在对话压缩前触发(手动/compact或自动压缩)。可以用来备份对话记录或保存关键上下文。
#!/usr/bin/env python3
"""PreCompact Hook: 备份压缩前的对话记录"""
import json
import sys
import shutil
from pathlib import Path
from datetime import datetime
def main():
try:
data = json.load(sys.stdin)
transcript_path = data.get('transcript_path', '')
trigger = data.get('trigger', 'unknown')
if transcript_path and Path(transcript_path).exists():
# 创建备份目录
backup_dir = Path('logs') / 'transcript_backups'
backup_dir.mkdir(parents=True, exist_ok=True)
# 生成带时间戳的备份文件名
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
session = Path(transcript_path).stem
backup = backup_dir / f"{session}_pre_compact_{trigger}_{ts}.jsonl"
shutil.copy2(transcript_path, backup)
sys.exit(0)
except Exception:
sys.exit(0)
if __name__ == '__main__':
main()
将前面所有案例整合为一个完整的项目级配置。
my-project/
├── .claude/
│ ├── settings.json # Hooks注册配置
│ ├── hooks/
│ │ ├── pre_tool_use.py # 安全护栏
│ │ ├── post_tool_use.py # 自动格式化
│ │ ├── user_prompt.py # Prompt预处理
│ │ ├── session_start.py # 会话引导
│ │ ├── stop.py # 完成检查
│ │ ├── notification.py # 桌面通知
│ │ └── pre_compact.py # 上下文备份
│ └── data/
│ └── sessions/ # 会话数据存储
├── logs/ # 审计日志(gitignore)
│ └── .gitkeep
└── .gitignore
{
"permissions": {
"allow": [
"Bash(npm:*)",
"Bash(git:*)",
"Bash(npx:*)",
"Bash(python3:*)",
"Bash(pytest:*)",
"Read",
"Glob",
"Grep"
],
"deny": []
},
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/session_start.py"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash|Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/pre_tool_use.py"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/post_tool_use.py"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/user_prompt.py"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/stop.py"
}
]
}
],
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/notification.py"
}
]
}
],
"PreCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/pre_compact.py"
}
]
}
]
}
}

图表 3
当hook行为不符合预期时,逐步排查:
1. 检查hook是否被加载
# 查看生效的settings配置
cat .claude/settings.json | python3 -m json.tool
2. 手动测试hook脚本
# 模拟PreToolUse输入
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | \
python3 .claude/hooks/pre_tool_use.py
echo "Exit code: $?"
3. 添加日志输出
在hook脚本中添加日志记录:
import datetime
def log(msg: str):
with open('logs/hooks_debug.log', 'a') as f:
ts = datetime.datetime.now().isoformat()
f.write(f"[{ts}] {msg}\n")
4. 理解stderr路由
关键规则:
- PreToolUse exit 2 → stderr给Claude(Claude会读并调整)
- UserPromptSubmit exit 2 → stderr给用户(Claude看不到)
- Stop exit 2 → stderr给Claude(Claude继续工作)
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Hook没有触发 | Matcher写错了 | 检查tool_name大小写,用空字符串测试 |
| stdout没注入上下文 | 只有UserPromptSubmit和SessionStart支持 |
用hookSpecificOutput.additionalContext |
| 阻止了但Claude没看到原因 | stderr路由到了用户而非Claude | 检查事件类型,用JSON输出代替stderr |
| Hook超时 | 脚本执行时间过长 | 给subprocess加timeout,hook总时限约30秒 |
| Python模块缺失 | 使用了第三方库 | 用uv run --script加inline依赖声明 |
对于需要第三方库的hook脚本,使用uv的inline script metadata:
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "python-dotenv",
# "requests",
# ]
# ///
import requests
# ... 正常代码
然后在settings.json中用uv run调用:
{
"type": "command",
"command": "uv run .claude/hooks/notification.py"
}
{
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{"type": "command", "command": "python3 .claude/hooks/check_bash.py"}
]
},
{
"matcher": "Write|Edit",
"hooks": [
{"type": "command", "command": "python3 .claude/hooks/check_file_write.py"}
]
},
{
"matcher": "",
"hooks": [
{"type": "command", "command": "python3 .claude/hooks/log_all_tools.py"}
]
}
]
}
空字符串Matcher匹配所有工具,放在最后作为兜底日志。
#!/usr/bin/env python3
import os
# 通过环境变量控制是否启用某个hook
ENABLE_STRICT_MODE = os.getenv('CLAUDE_HOOK_STRICT', 'false') == 'true'
ENABLE_NOTIFICATIONS = os.getenv('CLAUDE_HOOK_NOTIFY', 'true') == 'true'
if not ENABLE_STRICT_MODE:
sys.exit(0) # 跳过检查
在.bashrc或.zshrc中设置:
export CLAUDE_HOOK_STRICT=true # 严格模式
export CLAUDE_HOOK_NOTIFY=true # 开启通知
除了本地命令,hook还支持HTTP端点:
{
"type": "http",
"url": "https://your-server.com/api/clause-hook"
}
HTTP hook会收到POST请求,body是相同的JSON输入。服务器需要返回对应的JSON输出。这适合: - 集成企业内部审批系统 - 发送Webhook到Slack/Discord - 连接CI/CD系统
Hook还可以调用LLM做智能决策:
{
"type": "llm",
"prompt": "分析这个Bash命令是否安全。如果涉及rm、curl pipe bash、sudo等危险操作,返回BLOCKED。"
}
每个hook的执行时间直接影响Claude Code的响应速度。实测数据:
| Hook操作 | 典型耗时 | 优化建议 |
|---|---|---|
| 纯Python判断 | <50ms | 无需优化 |
| Git命令 | 50-200ms | 减少git调用次数 |
| npm/npx命令 | 1-5s | 仅在文件变更时触发 |
| 格式化单个文件 | 200ms-1s | 使用matcher精确匹配 |
| LLM调用 | 2-10s | 仅在关键决策点使用 |
sys.exit(0)Notification事件而非PostToolUse| 维度 | 评估 |
|---|---|
| 适合训练营类型 | AI开发工具训练营、DevOps自动化训练营、企业AI治理训练营 |
| 预计教学时长 | 4-6小时(含动手实验) |
| 难度等级 | 中高级(需要Python/Shell脚本和CLI基础) |
| 实验环境要求 | 本地开发机 + Claude Code CLI + Git项目 |
| 建议分组 | 每人独立完成,最后小组分享各自创建的hook套件 |
| 前置课程 | Claude Code基础使用、CLAUDE.md配置 |
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
