Claude Code Hooks自动化:让AI自动执行你的规则
Claude Code Hooks系统完整实战教程,涵盖6种Hook类型、配置格式、决策输出机制,以及自动代码格式化、文件保护、Git提交质量检查三大实战场景,附FAQ和安全最佳实践。
Claude Code Hooks自动化:让AI自动执行你的规则
阅读时间:约 18 分钟 | 难度:入门级 | 前置要求:已完成 Claude Code 安装和基础使用,了解
.claude/settings.json配置方式(见 05-custom-commands.md)
作者:林宁
访问官网:weelinking.com
TL;DR
Hooks 是 Claude Code 的"自动化传感器"–在特定事件(工具调用前后、用户提交输入、会话开始结束等)发生时,自动执行你指定的脚本。与在 CLAUDE.md 中写提示词不同,Hooks 是确定性执行的,不存在 AI "忘记"的问题。只需在 .claude/settings.json 中声明 Hook 类型、匹配器和脚本路径,就能实现代码自动格式化、危险文件保护、Git 提交质量检查等工作流,一次配置全员生效。
你将学到什么
- 理解 Hooks 的核心价值及其与手动提示词方式的区别
- 掌握全部 6 种 Hook 类型的触发时机和典型用途
- 5 分钟创建并验证第一个 PostToolUse Hook
- 读懂 settings.json 中 hooks 字段的完整结构和 matcher 匹配规则
- 理解 allow / deny / ask / message 四种决策输出
- 动手实现自动代码格式化、文件保护、Git 提交检查三个实战场景
- 掌握安全警告和最佳实践
什么是 Hooks?为什么需要它?
传感器类比
把 Claude Code 想象成一辆智能汽车。Hooks 就是车上的传感器系统:胎压传感器在气压异常时自动报警,碰撞传感器在撞击时自动弹出安全气囊。你不需要每次上车前叮嘱"记得检查胎压"–传感器会 100% 执行。
手动提示词 vs Hooks
| 对比维度 | 提示词 / CLAUDE.md | Hooks |
|---|---|---|
| 可靠性 | AI 可能忘记执行 | 100% 确定性触发 |
| 一致性 | 每次可能不同 | 每次完全相同 |
| 自动化 | 需要 AI 主动遵循 | 事件驱动自动执行 |
| 团队协作 | 每人都要提醒 | 配置一次全员生效 |
| 适用场景 | 灵活建议、上下文说明 | 强制规则、自动化流水线 |
一句话总结:提示词是"建议",Hooks 是"规则"。
6 种 Hook 类型一览
💡 国内稳定访问 Claude: weelinking - 稳定、稳定、稳定
Claude Code 提供以下 6 种 Hook 事件(外加 Stop),覆盖从会话启动到结束的完整生命周期:
| Hook 类型 | 触发时机 | 典型用途 | 能否阻止后续操作 |
|---|---|---|---|
| PreToolUse | 工具调用前 | 权限校验、参数验证、危险命令拦截 | 是 |
| PostToolUse | 工具调用后 | 代码格式化、自动备份、质量检查 | 否 |
| UserPromptSubmit | 用户输入提交后、AI 处理前 | 提示词增强、敏感词过滤 | 是 |
| Notification | 通知发送时 | 桌面推送、日志记录 | 否 |
| SessionStart | 会话开始时 | 环境检查、依赖校验 | 否 |
| SessionEnd | 会话结束时 | 清理临时文件、保存状态 | 否 |
| Stop | AI 停止响应时 | 状态保存、会话记录 | 否 |
执行流程可以简化为:
# 完整生命周期
用户输入 --> [UserPromptSubmit] --> Claude 处理 --> 决定调用工具
--> [PreToolUse] --> 执行工具 --> [PostToolUse] --> 返回结果
5 分钟快速开始:创建第一个 Hook
💡 国内稳定访问 Claude: weelinking - 稳定、稳定、稳定
下面用一个 PostToolUse Hook 演示完整流程:每次 Claude 写入文件后,自动打印一条提示。
第一步:创建 Hook 脚本
在项目根目录创建 .claude/hooks/ 目录,然后新建 post-write-hello.py:
#!/usr/bin/env python3
"""
PostToolUse Hook 示例
每次 Write 工具执行后打印提示
"""
import sys
import json
# 从 stdin 读取工具执行信息(JSON 格式)
try:
input_data = json.loads(sys.stdin.read())
except Exception:
sys.exit(0)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
file_path = tool_input.get("file_path", "")
if tool_name == "Write":
# stderr 输出会显示在 Claude Code 界面
print(f"[Hook] 文件已保存: {file_path}", file=sys.stderr)
sys.exit(0)
macOS / Linux 用户需要添加执行权限:
chmod +x .claude/hooks/post-write-hello.py
第二步:配置 settings.json
创建或编辑 .claude/settings.json,写入以下内容:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/post-write-hello.py",
"timeout": 10
}
]
}
]
}
}
第三步:验证触发
重启 Claude Code,然后输入:
# 在 Claude Code 对话中输入:
# "帮我创建一个 test.txt 文件,内容是 Hello Hooks"
预期看到类似输出:
# Claude 执行 Write 工具后,Hook 自动打印:
# [Hook] 文件已保存: /your/project/test.txt
如果没有看到输出,用以下命令手动验证脚本是否正常:
echo '{"tool_name":"Write","tool_input":{"file_path":"test.txt"}}' | python .claude/hooks/post-write-hello.py
# 预期输出到 stderr: [Hook] 文件已保存: test.txt
配置格式详解
hooks 字段结构
.claude/settings.json 中 hooks 的完整结构如下:
{
"hooks": {
"<HookType>": [
{
"matcher": "<工具名匹配规则>",
"hooks": [
{
"type": "command",
"command": "<要执行的命令>",
"timeout": 10
}
]
}
]
}
}
各字段说明:
| 字段 | 必填 | 说明 |
|---|---|---|
HookType |
是 | PreToolUse / PostToolUse / UserPromptSubmit / Notification / SessionStart / SessionEnd / Stop |
matcher |
否 | 工具名匹配规则,支持 Write(精确)、Write|Edit(多选)、.*(全部)。仅 PreToolUse 和 PostToolUse 需要 |
type |
是 | 固定为 "command" |
command |
是 | 要执行的 Shell 命令或脚本路径 |
timeout |
否 | 超时秒数,默认 60。建议简单检查设 5-10,格式化设 30,完整测试设 120 |
matcher 匹配器示例
"matcher": "Write" // 精确匹配 Write 工具
"matcher": "Write|Edit" // 匹配 Write 或 Edit
"matcher": "Bash" // 精确匹配 Bash 工具
"matcher": ".*" // 匹配所有工具(慎用)
同一个 HookType 下可以配置多组 matcher,每组 matcher 下可以挂载多个脚本,按顺序依次执行。
Hook 决策输出:控制工具是否执行
PreToolUse Hook 可以通过 stdout 输出 JSON 决策来控制工具行为:
{"decision": "deny", "message": "禁止修改此文件"}
四种决策值及含义:
| decision 值 | 含义 | 工具是否执行 |
|---|---|---|
"allow" |
明确允许 | 是 |
"deny" |
拒绝执行,Claude 收到错误提示 | 否 |
"ask" |
暂停并询问用户是否继续 | 等待用户决定 |
"message" |
仅显示消息,不影响执行 | 是 |
| 无输出 | 默认允许 | 是 |
PostToolUse 和其他 Hook 类型不返回决策(工具已经执行完毕),只能通过 stderr 输出日志或执行后处理任务。
实战:自动代码格式化
需求
每次 Claude 写入 .js / .ts / .py 等文件后,自动运行对应的格式化工具(Prettier / Black)。
Hook 脚本
创建 .claude/hooks/post-auto-format.py:
#!/usr/bin/env python3
"""PostToolUse Hook - 保存代码后自动格式化"""
import sys
import json
import subprocess
from pathlib import Path
FORMATTERS = {
".js": 'npx prettier --write "{file}"',
".ts": 'npx prettier --write "{file}"',
".jsx": 'npx prettier --write "{file}"',
".tsx": 'npx prettier --write "{file}"',
".py": 'black "{file}"',
}
EXCLUDED = {"node_modules", "dist", "build", ".git", "__pycache__"}
try:
data = json.loads(sys.stdin.read())
except Exception:
sys.exit(0)
if data.get("tool_name") != "Write":
sys.exit(0)
file_path = data.get("tool_input", {}).get("file_path", "")
path = Path(file_path)
# 跳过排除目录
if any(part in EXCLUDED for part in path.parts):
sys.exit(0)
cmd_template = FORMATTERS.get(path.suffix)
if not cmd_template:
sys.exit(0)
cmd = cmd_template.format(file=file_path)
try:
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
print(f"[AutoFormat] {path.name}: 格式化成功", file=sys.stderr)
else:
print(f"[AutoFormat] {path.name}: 失败 - {result.stderr[:80]}", file=sys.stderr)
except subprocess.TimeoutExpired:
print(f"[AutoFormat] {path.name}: 超时", file=sys.stderr)
except FileNotFoundError:
print(f"[AutoFormat] 格式化工具未安装", file=sys.stderr)
配置
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/post-auto-format.py",
"timeout": 30
}
]
}
]
}
}
验证效果:让 Claude 创建一个 .js 文件,观察是否出现 [AutoFormat] app.js: 格式化成功 提示。
实战:文件保护规则
需求
禁止 Claude 写入或编辑 production/ 目录下的任何文件。
Hook 脚本
创建 .claude/hooks/pre-protect-production.py:
#!/usr/bin/env python3
"""PreToolUse Hook - 保护 production 目录"""
import sys
import json
try:
data = json.loads(sys.stdin.read())
except Exception:
sys.exit(0)
if data.get("tool_name") not in ("Write", "Edit"):
sys.exit(0)
file_path = data.get("tool_input", {}).get("file_path", "").replace("\\", "/")
PROTECTED = ["production/", "prod/", ".env"]
for p in PROTECTED:
if p in file_path:
decision = {
"decision": "deny",
"message": f"禁止修改受保护路径: {file_path} (匹配规则: {p})"
}
print(json.dumps(decision, ensure_ascii=False))
sys.exit(0)
sys.exit(0)
配置
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/pre-protect-production.py",
"timeout": 5
}
]
}
]
}
}
当 Claude 尝试修改 production/config.json 时,会收到拒绝提示并停止操作。
实战:Git 提交质量检查
需求
在 Claude 执行 git commit 前,自动运行分支保护检查、敏感信息扫描和代码 lint。
Hook 脚本
创建 .claude/hooks/git-pre-commit-checker.py:
#!/usr/bin/env python3
"""PreToolUse Hook - Git 提交前质量检查"""
import sys
import json
import subprocess
import re
CONFIG = {
"protected_branches": ["main", "master", "production"],
"secret_patterns": [
r"(?i)(api[_-]?key|apikey)\s*[=:]\s*[\"']?[\w-]{20,}",
r"sk-[a-zA-Z0-9]{20,}",
r"ghp_[a-zA-Z0-9]{36,}",
],
}
def run_cmd(cmd):
try:
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60)
return r.returncode, r.stdout, r.stderr
except Exception as e:
return -1, "", str(e)
def check_branch():
code, out, _ = run_cmd("git rev-parse --abbrev-ref HEAD")
branch = out.strip()
if branch in CONFIG["protected_branches"]:
return False, f"禁止直接提交到受保护分支: {branch}"
return True, f"当前分支: {branch}"
def check_secrets():
code, out, _ = run_cmd("git diff --cached")
for pattern in CONFIG["secret_patterns"]:
if re.search(pattern, out):
return False, f"发现可疑敏感信息 (匹配: {pattern[:30]}...)"
return True, "敏感信息检查通过"
def check_lint():
code, out, _ = run_cmd("git diff --cached --name-only --diff-filter=ACMR")
py_files = [f for f in out.strip().split("\n") if f.endswith(".py")]
if py_files:
rc, stdout, stderr = run_cmd(f'ruff check {" ".join(py_files)}')
if rc != 0:
return False, f"Python lint 问题:\n{stdout or stderr}"
return True, "代码风格检查通过"
try:
data = json.loads(sys.stdin.read())
except Exception:
sys.exit(0)
if data.get("tool_name") != "Bash" or "git commit" not in data.get("tool_input", {}).get("command", ""):
sys.exit(0)
checks = [("分支检查", check_branch), ("敏感信息", check_secrets), ("代码风格", check_lint)]
all_passed = True
print("\n" + "=" * 50, file=sys.stderr)
print("Git 提交前检查报告", file=sys.stderr)
print("=" * 50, file=sys.stderr)
for name, fn in checks:
passed, msg = fn()
status = "PASS" if passed else "FAIL"
print(f" [{status}] {name}: {msg}", file=sys.stderr)
if not passed:
all_passed = False
print("=" * 50, file=sys.stderr)
if not all_passed:
print(json.dumps({"decision": "ask", "message": "部分检查未通过,是否仍要继续提交?"}, ensure_ascii=False))
配置
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/git-pre-commit-checker.py",
"timeout": 120
}
]
}
]
}
}
这个 Hook 会在检测到 git commit 命令时自动运行三项检查,任一未通过则弹出确认提示,由用户决定是否继续。
安全警告与最佳实践
Hooks 能执行任意 Shell 命令,配置不当可能造成文件删除、信息泄露等后果。务必遵守以下原则:
安全清单
| 风险 | 防护措施 |
|---|---|
| 恶意脚本 | 只运行你自己编写或审查过的脚本,不要从不明来源复制配置 |
| 权限过大 | 脚本不使用 sudo,只请求必要权限 |
| 敏感信息 | 不在脚本中硬编码密码 / Token,使用环境变量 |
| 无限循环 | 设置合理的 timeout 值,避免脚本卡死 |
| 团队透明度 | 将 .claude/settings.json 和 .claude/hooks/ 纳入代码审查 |
错误处理模板
每个 Hook 脚本都应包含以下防御逻辑:
#!/usr/bin/env python3
import sys
import json
try:
input_data = json.loads(sys.stdin.read())
except json.JSONDecodeError:
# JSON 解析失败时静默退出,不影响 Claude 正常工作
sys.exit(0)
# 业务逻辑...
# 无论执行结果如何,确保正常退出
sys.exit(0)
关键点:Hook 脚本出错不会阻止 Claude Code 运行,但该 Hook 功能会失效。始终用 try/except 包裹 JSON 解析,并在末尾显式 sys.exit(0)。
FAQ
Hook 脚本支持哪些语言?
任何可以从命令行执行的程序都可以作为 Hook 脚本:Python(推荐,跨平台)、Bash(macOS/Linux)、Batch(Windows)、Node.js、Go 或 Rust 编译后的二进制文件。只要 command 字段指定的命令能在终端运行即可。
Hook 失败会影响 Claude 运行吗?
不会。Hook 脚本报错或超时只会导致该 Hook 本身功能失效,Claude Code 会继续正常工作。但建议为每个脚本添加完善的错误处理,避免静默失败导致你误以为规则在生效。
怎么调试 Hook?
三个方法:
- 手动模拟输入:用
echo管道将模拟 JSON 传入脚本,观察 stdout 和 stderr 输出。 - 添加 stderr 日志:在脚本关键位置
print("DEBUG: ...", file=sys.stderr)输出调试信息,会显示在 Claude Code 界面。 - 写入日志文件:将调试信息写入
~/.claude/hooks-debug.log,方便事后分析。
echo '{"tool_name":"Write","tool_input":{"file_path":"test.js"}}' | python .claude/hooks/post-auto-format.py
# 观察 stderr 输出的调试信息
Hook 和 Commands(自定义斜杠命令)有什么区别?
Commands 是用户主动调用的快捷指令(如 /review),需要手动触发;Hooks 是事件驱动的自动化脚本,在满足条件时自动执行。Commands 适合"按需操作",Hooks 适合"强制规则"。两者可以配合使用:比如用 Command 触发代码审查,用 Hook 自动格式化审查后的修改。
Hooks 可以链式调用吗?
可以。在同一个 matcher 下配置多个脚本,它们会按顺序依次执行:
{
"matcher": "Write",
"hooks": [
{"type": "command", "command": "python hook1.py", "timeout": 10},
{"type": "command", "command": "python hook2.py", "timeout": 10}
]
}
但注意,对于 PreToolUse,如果前一个脚本返回 deny,后续脚本不会执行。建议将优先级最高的检查放在前面。
总结与下一步
本文覆盖了 Claude Code Hooks 系统的核心知识:
- Hooks = 确定性自动化:不依赖 AI 记忆,事件触发 100% 执行
- 6 种 Hook 类型:覆盖从用户输入到会话结束的完整生命周期
- 配置三要素:Hook 类型 + matcher 匹配器 + command 脚本
- 决策输出:PreToolUse 通过 allow / deny / ask / message 控制工具行为
- 三大实战场景:自动格式化、文件保护、Git 提交检查
- 安全第一:只用信任脚本,设置 timeout,完善错误处理
下一步阅读:
- 上一篇:自定义 Commands 开发 – 创建可复用的斜杠命令
- 下一篇:Skills 系统与技能包 – 为 Claude Code 定制专业技能
系列文章:本文是《Claude Code实战指南》系列第 6 篇,查看完整系列
💡 国内稳定访问 Claude: weelinking - 稳定、稳定、稳定
更多推荐


所有评论(0)