Claude Code Hooks:为AI编程注入工程纪律的自动化钩子系统

Claude Code HooksPreToolUsePostToolUse
于 2026-07-05 05:30:04 修改
·本内容遵循CC 4.0 BY-SA版权协议

1. 项目概述:为什么你需要 Claude Code Hooks,而不是继续“人工喊话”

你有没有过这种体验:刚让 Claude Code 写完一个 Python 脚本,顺手点开文件一看——缩进全乱了,函数名是 get_data_from_api_v2_final_really_final.py,连 docstring 都没加;你叹了口气,敲出 /format with black,它又问:“需要我运行测试吗?”你翻白眼:“当然要啊,上回漏测导致线上报错你还记得吗?”它乖巧点头,跑完测试又弹出通知:“检测到未提交的 git 变更,是否提交?”你手指悬在键盘上,心里默念:“求你别再问我了……我自己来。”

这不是 AI 不够聪明,而是它根本没有工作记忆、没有上下文契约、也没有执行惯性。它每次响应都是“全新开始”,像一个极度优秀的实习生,但没人给他配一本《团队开发守则》和一套自动打卡机。你反复提醒的“格式化”“跑测试”“加 license 头”“别碰 /etc/ 目录”,对它而言只是本次对话里的一句临时指令,下一次 prompt 一刷新,前功尽弃。

Claude Code Hooks 就是这本守则 + 这台打卡机的合体。它不是让你写更多 prompt,而是把你的开发规范编译成操作系统能听懂的指令,嵌入到 Claude Code 的行为流水线里。当它准备写文件时,Hook 在它落笔前拦住:“等等,这个路径是 /etc/nginx.conf?不行,退出。”当它写完代码后,Hook 自动接手:“好,用 black 格式化,跑 pytest,生成覆盖率报告,把结果写进 transcript。”整个过程你完全不用开口,就像给 IDE 装上了隐形的 CI/CD 引擎。

它解决的不是“能不能写代码”的问题,而是“写出来的代码能不能直接进生产环境”的问题。关键词不是“自动化”,而是可审计、可复现、可强制的工程纪律。适合三类人:

  • 个人开发者:厌倦了每次写完代码都要手动补七八个步骤;
  • 小团队技术负责人:想统一新成员的代码风格,又不想天天 review 缩进;
  • 安全敏感型项目成员:需要确保任何 API key、密钥、生产配置都不会被意外写入仓库。

这不是锦上添花的玩具,而是把 AI 从“高级打字员”升级为“持证上岗的开发协作者”的关键一步。接下来,我会带你从零搭建一套真正能落地的 Hook 系统——不讲虚概念,只拆解你明天就能抄作业的实操细节。

2. 核心设计逻辑:为什么是事件+匹配器+命令,而不是“AI 懂我想要什么”

很多新手第一反应是:“既然 Claude 很强,那我直接说‘每次写完代码都帮我格式化并跑测试’不就行了?”——这恰恰是 Hook 存在的根本原因。AI 的“理解”是语义级的、概率性的、上下文受限的;而工程规范必须是语法级的、确定性的、无歧义的。我们来拆解 Hook 架构的三层设计,每一层都在堵死一个“AI 可能犯错”的漏洞。

2.1 事件(Event):锚定动作发生的精确时间戳

Claude Code 的内部行为流是一条清晰的管道:你输入 prompt → 它规划工具调用 → 执行 Write/Edit/RunCommand → 返回结果 → 更新对话历史。Hook 的事件就是在这条管道上预设的10 个标准卡口,每个卡口对应一个不可跳过的原子动作节点。官方文档列了 10 个,但实际高频使用的就 4 个,其他属于“特种兵”:

事件名 触发时机 典型用途 为什么不能用别的代替
PreToolUse 工具执行前(如 Write 文件前) 安全闸门:检查文件路径、内容敏感词、权限范围 若用 PostToolUse,文件已写入磁盘,删都删晚了
PostToolUse 工具执行后(如 Write 完成后) 质量守门员:格式化、测试、备份、日志记录 PreToolUse 拿不到执行结果(如 filePath 是否真实创建)
UserPromptSubmit 你按下回车发送 prompt 的瞬间 会话治理:自动注入项目背景、拦截高危指令(如“删库”)、记录需求原始表述 这是唯一能捕获“人类意图源头”的事件,比分析 AI 回复可靠十倍
PermissionRequest Claude 弹出“是否允许运行命令?”对话框时 信任代理:自动批准可信命令(如 git status),拒绝高危命令(rm -rf) 避免你因走神点错“允许”,这是人机协作的信任交接点

提示:别迷信“智能匹配”。我见过团队用 UserPromptSubmit 做全文关键词扫描,只要 prompt 里出现 “delete” 就自动拦截并回复:“检测到删除指令,请确认是否为误操作?如需清理缓存,请使用 /clear_cache 命令。”——这比让 AI 理解“delete”在当前语境下是删数据库还是删日志文件,稳定一万倍。

2.2 匹配器(Matcher):用正则表达式做精准手术刀

匹配器不是模糊搜索,而是正则表达式(regex)。它的作用不是“找相似”,而是“严格匹配工具名称”。比如 Write 这个 matcher,只会触发 Write 工具,不会触发 EditRunCommand。这杜绝了“我想格式化 Python 文件,结果它把 Dockerfile 也格式化了”的灾难。

常见 matcher 写法与陷阱:

  • 精确匹配"matcher": "Write" —— 只响应 Write 工具,最安全,推荐新手起步。
  • 多工具匹配"matcher": "Write\|Edit" —— 注意 \| 是转义后的竖线,匹配 WriteEdit。适用于“所有文件变更都需备份”的场景。
  • 前缀匹配"matcher": "Notebook.*" —— 匹配所有以 Notebook 开头的工具(如 NotebookExecute, NotebookSave)。适合 Jupyter 工作流。
  • 万能匹配(慎用)"matcher": ".*""matcher": "" —— 响应所有工具。看似方便,实则埋雷:一个 say "done" 命令会每秒响 20 次,你会疯掉。

注意:matcher 只匹配工具名,不匹配文件内容或路径。想根据 .py 文件做判断?必须在你的 hook 脚本里解析 JSON 输入里的 file_path 字段。这是设计上的刻意分离:事件决定“何时触发”,matcher 决定“触发哪个工具”,脚本逻辑决定“具体做什么”。

2.3 命令(Command):Shell 是唯一真理,Python 脚本是最佳搭档

Hook 的 command 字段最终会被系统 exec() 调用,这意味着它必须是操作系统原生可执行的命令python3 ~/.claude/format.py 可行,black . 可行,但 Claude, please format this 绝对不行。

为什么强烈推荐用 Python 脚本而非纯 shell 命令?三个血泪教训:

  1. JSON 解析不可绕过:Hook 输入是标准 JSON 流,shell 本身不带 JSON 解析器。用 jq?Linux/macOS 可能有,Windows 默认没有。Python 的 json.load(sys.stdin) 一行搞定,跨平台稳如老狗。
  2. 错误处理必须显式:Shell 命令失败默认静默。你想让 Claude 知道“black 格式化失败了”,必须主动 echo "error" >&2 && exit 2。Python 里 try/except + sys.stderr.write() + sys.exit(2) 逻辑清晰,不易遗漏。
  3. 路径与环境隔离:Claude Code 启动时的工作目录(cwd)可能和你终端不同。Shell 命令 cd myproject && black . 在 hook 里可能因路径错误失败。Python 脚本里 os.chdir(input_data['cwd']) 一行重置,绝对可靠。

实操心得:我的所有生产级 Hook 脚本第一行必写 #!/usr/bin/env python3,第二行必写 import sys, json, os。第三行永远是 input_data = json.load(sys.stdin)。这三行是 Hook 的“宪法”,缺一不可。别图省事写单行命令,调试时你会感谢自己。

3. 实操全流程:从第一个“叮”声到全自动 CI 流水线

现在,我们抛弃所有理论,直接进入键盘实战。我会带你一步步搭建一个真实可用、即装即用的 Hook 系统,包含:基础通知、安全防护、智能格式化、Git 自动化四个核心模块。所有命令、路径、脚本内容均经过 macOS/Linux/Windows(WSL)三端实测。

3.1 环境准备:5 分钟完成所有前置依赖

别跳过这步!90% 的 Hook 失败源于环境没配对。

  1. 确认 Claude Code 版本:打开 Claude Code,输入 /version。确保是 v2.5.0 或更高版本(低版本不支持 Hook)。若版本过低,去官网下载最新版,旧版 Hook 配置不兼容。
  2. 验证 Shell 基础命令(任选其一):
    • macOS:打开终端,运行 say "test",应听到语音。
    • Linux:运行 which notify-send,若返回 /usr/bin/notify-send,说明已安装;否则 sudo apt install libnotify-bin(Ubuntu/Debian)或 sudo yum install libnotify(CentOS/RHEL)。
    • Windows(WSL):确保已安装 gdbusnotify-send(通过 sudo apt install libnotify-bin);若用原生 Windows,用 PowerShell:[console]::beep() 测试蜂鸣器。
  3. 创建 Hook 工作目录
    BASH
    # 创建全局 Hook 目录(所有项目生效)
    mkdir -p ~/.claude
    # 创建项目级 Hook 目录(仅当前项目生效,优先级更高)
    cd /path/to/your/project
    mkdir -p .claude
  4. 设置 Python 脚本权限(关键!):
    BASH
    # 创建一个测试脚本
    echo '#!/usr/bin/env python3\nprint("Hello from Hook!")' > ~/.claude/test_hook.py
    # 赋予可执行权限(Linux/macOS 必须!Windows WSL 同样需要)
    chmod +x ~/.claude/test_hook.py
    # 测试能否执行
    ~/.claude/test_hook.py # 应输出 Hello from Hook!

注意:Windows 原生系统用户,若无法使用 chmod,请确保脚本保存为 .py 后缀,并在 Hook 配置中明确调用 python3(如 "command": "python3 ~/.claude/test_hook.py")。WSL 用户务必执行 chmod +x,否则 Hook 会报 Permission denied

3.2 第一个 Hook:让 Claude 写完文件时“叮”一声(含完整排错指南)

这是 Hook 的“Hello World”,但我要给你远超 Hello World 的排错能力。

步骤 1:用交互命令 /hooks 快速创建

  • 在 Claude Code 聊天框输入 /hooks,回车。
  • 选择 PostToolUse(工具执行后触发)。
  • 选择 Add new hook
  • Matcher 输入 Write(精确匹配写文件)。
  • Command 输入(按你的系统选一个):
    • macOS:say "File written"
    • Linux:notify-send "Claude" "File written"
    • Windows (WSL):gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.Notify "Claude" 0 "info" "File written" "" [] {} 5000
  • 按提示保存,退出。

步骤 2:手动验证配置文件 打开 ~/.claude/settings.json,你应该看到类似结构:

JSON
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "say \"File written\""
}
]
}
]
}
}

注意:command 字符串里的双引号必须用反斜杠转义,否则 JSON 解析失败。

步骤 3:终极测试与排错(这才是重点)

  • 让 Claude 写一个文件:/write hello.py,内容随意。
  • 预期现象:听到语音/弹窗,且 Claude transcript(Ctrl+O)里显示 Hook executed: say "File written"
  • 如果没反应?按顺序排查
    1. 检查 transcript 错误:按 Ctrl+O,看是否有 Hook execution failed: command not found。若有,说明 say 命令不存在(macOS 13+ 可能需开启语音权限)。
    2. 检查文件权限:在终端运行 ls -l ~/.claude/settings.json,确认文件可读;运行 cat ~/.claude/settings.json | python3 -m json.tool,验证 JSON 格式正确(无多余逗号、引号闭合)。
    3. 检查事件是否触发:在 transcript 里搜索 PostToolUse,确认 Claude 确实执行了 Write 工具(如 Tool: Write, Input: {"file_path":"hello.py",...})。
    4. 强制重载配置:关闭 Claude Code,重新打开;或再次输入 /hooks,选择 Reload configuration

实操心得:我第一次部署时,say 命令在 transcript 里报错 command not found,查了半小时才发现是 macOS 系统偏好设置 → 辅助功能 → 语音 → 关闭了“启用文本转语音”。Hook 的第一道防线永远是系统级权限,不是代码逻辑。

3.3 安全防护 Hook:阻止一切对 /etc/production.yml 的修改

这是保护你服务器和生产环境的生命线。原理很简单:在 PreToolUse 事件拦截,检查 tool_input.file_path,命中危险路径则 exit 2 并向 Claude 报告。

创建安全脚本 ~/.claude/security_guard.py

PYTHON
# !/usr/bin/env python3
import json
import sys
import os
 
# 1. 读取 Claude 传入的 JSON
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
print(f"JSON parse error: {e}", file=sys.stderr)
sys.exit(2)
 
# 2. 提取关键信息
tool_input = input_data.get("tool_input", {})
file_path = tool_input.get("file_path", "")
tool_name = input_data.get("tool_name", "")
 
# 3. 定义危险模式(绝对路径 + 文件名)
dangerous_patterns = [
"/etc/", # 系统配置目录
"/usr/local/etc/", # 全局配置
"production.yml", # 常见生产配置
"secrets.env", # 环境变量文件
"database.conf", # 数据库配置
]
 
# 4. 检查是否匹配(注意:file_path 可能是相对路径,需补全)
# Claude 传入的 file_path 通常是相对于 cwd 的,但我们也要防绝对路径
is_dangerous = False
for pattern in dangerous_patterns:
if pattern in file_path or file_path.startswith(pattern):
is_dangerous = True
break
 
# 5. 决策与反馈
if is_dangerous:
# ⚠️ 关键:用 stderr 输出,exit 2,Claude 会收到并解释给用户
error_msg = f"🚨 SECURITY BLOCK: Attempt to {tool_name} '{file_path}'. This path is restricted for production safety."
print(error_msg, file=sys.stderr)
sys.exit(2) # Claude 会停止执行该工具
else:
# ✅ 允许执行,stdout 输出到 transcript 供审计
print(f"✅ Security check passed for {tool_name} '{file_path}'")
sys.exit(0)

配置 Hook(~/.claude/settings.json):

JSON
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|RunCommand",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/security_guard.py"
}
]
}
]
}
}

测试方法:

  • 让 Claude 执行:/edit /etc/hosts/write production.yml
  • 预期结果:Claude 立即停止,transcript 显示红色错误:“SECURITY BLOCK...”,并建议你改用开发环境配置。
  • 为什么用 PreToolUse 因为 Write 工具一旦执行,文件就已写入。PreToolUse 是最后一道物理防线。

注意事项:此脚本中的 dangerous_patterns 列表必须根据你的实际环境定制。例如,你的项目里 config/production.json 是合法的,那就不能加 production.json。安全策略永远是“最小权限”,而非“一刀切”。

3.4 智能格式化 Hook:只对 .py .js .ts 文件运行 Black/Prettier

基础 Hook 会无差别格式化所有文件,但 package-lock.jsonprettier 格式化后可能失效。我们需要精准识别文件类型。

创建格式化脚本 ~/.claude/smart_formatter.py

PYTHON
# !/usr/bin/env python3
import json
import sys
import os
import subprocess
 
def run_command(cmd, cwd=None):
"""安全执行命令,捕获输出"""
try:
result = subprocess.run(
cmd,
shell=True,
cwd=cwd,
capture_output=True,
text=True,
timeout=30
)
return result.returncode == 0, result.stdout, result.stderr
except Exception as e:
return False, "", str(e)
 
# 1. 读取输入
input_data = json.load(sys.stdin)
tool_input = input_data.get("tool_input", {})
file_path = tool_input.get("file_path", "")
cwd = input_data.get("cwd", os.getcwd())
 
# 2. 提取文件扩展名和基名
_, ext = os.path.splitext(file_path.lower())
basename = os.path.basename(file_path)
 
# 3. 定义支持的格式化规则
format_rules = {
".py": ["black", "."],
".js": ["prettier", "--write", "."],
".ts": ["prettier", "--write", "."],
".html": ["prettier", "--write", "."],
".css": ["prettier", "--write", "."],
}
 
# 4. 匹配并执行
if ext in format_rules:
cmd_parts = format_rules[ext]
cmd = " ".join(cmd_parts)
# 执行格式化(注意:cwd 是项目根目录,不是文件所在目录)
success, stdout, stderr = run_command(cmd, cwd=cwd)
if success:
print(f"✅ Formatted {basename} with {cmd_parts[0]}")
sys.exit(0)
else:
error_msg = f"❌ Formatting failed for {basename}: {stderr[:200]}"
print(error_msg, file=sys.stderr)
sys.exit(2)
else:
# 不支持的文件类型,静默通过
print(f"ℹ️ Skipped formatting for {basename} (unsupported extension: {ext})")
sys.exit(0)

配置 Hook:

JSON
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/smart_formatter.py"
}
]
}
]
}
}

依赖安装(按需):

  • Python:pip install black
  • JavaScript:npm install -g prettier

实操心得:此脚本的关键在于 cwd=cwd 参数。Claude 传入的 cwd 是项目根目录,而 black . 必须在此目录下运行才能找到 pyproject.toml 配置。若你在子目录写文件,black 仍会全局格式化——这正是我们想要的:保证整个项目的风格统一。别试图“只格式化当前文件”,那违背了工程规范的本质。

3.5 Git 自动化 Hook:提交代码时自动生成专业 Commit Message

忘记写 commit message?消息写得像“fix bug”?Hook 可以帮你解决。

创建 Git Hook ~/.claude/git_auto_commit.py

PYTHON
# !/usr/bin/env python3
import json
import sys
import os
import subprocess
import tempfile
 
def run_git_cmd(cmd, cwd=None):
result = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True)
return result.returncode == 0, result.stdout.strip(), result.stderr.strip()
 
# 1. 读取输入
input_data = json.load(sys.stdin)
cwd = input_data.get("cwd", os.getcwd())
 
# 2. 获取当前分支和暂存区状态
success, branch, _ = run_git_cmd("git branch --show-current", cwd=cwd)
if not success or not branch:
print("⚠️ Not in a git repo or no branch detected", file=sys.stderr)
sys.exit(0) # 非 git 项目,静默通过
 
# 3. 检查是否有暂存变更
success, status, _ = run_git_cmd("git status --porcelain", cwd=cwd)
if not success or not status.strip():
print("ℹ️ No staged changes to commit")
sys.exit(0)
 
# 4. 生成 diff(用于 AI 分析)
success, diff, _ = run_git_cmd("git diff --cached", cwd=cwd)
if not success or not diff.strip():
print("⚠️ Could not generate diff", file=sys.stderr)
sys.exit(0)
 
# 5. 创建临时文件存 diff(Claude API 需要文件路径)
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.diff') as f:
f.write(diff)
diff_file = f.name
 
# 6. 调用 Claude API 生成 commit message(此处用模拟,实际需替换为你的 API 调用)
# 为演示,我们用一个简单规则:提取修改的文件名和变更行数
files_changed = [line.split()[-1] for line in status.split('\n') if line.strip()]
file_list = ", ".join(files_changed[:3]) + ("..." if len(files_changed) > 3 else "")
lines_changed = len(diff.split('\n'))
 
commit_msg = f"chore: auto-commit by Claude Hook\n\n- Modified {len(files_changed)} files: {file_list}\n- Lines changed: ~{lines_changed}"
 
# 7. 执行 git commit
commit_cmd = f'git commit -m "{commit_msg}"'
success, stdout, stderr = run_git_cmd(commit_cmd, cwd=cwd)
 
if success:
print(f"✅ Auto-committed on branch '{branch}': {commit_msg.split(chr(10))[0]}")
sys.exit(0)
else:
print(f"❌ Git commit failed: {stderr}", file=sys.stderr)
sys.exit(2)

配置(触发时机选 PostToolUse):

JSON
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/git_auto_commit.py"
}
]
}
]
}
}

注意:真实生产环境需集成 Anthropic API。将 diff_file 内容作为 prompt 发送给 Claude,要求:“请生成一条符合 conventional commits 规范的 commit message,聚焦于代码变更的功能影响,不要提技术细节。输出仅限一行 message,无额外字符。”——这比任何正则匹配都准。

4. 高频问题与硬核排错:那些官方文档不会告诉你的坑

Hook 看似简单,但实际部署时 80% 的问题都出在“看不见的角落”。以下是我在 37 个不同项目中踩过的坑,按发生频率排序。

4.1 Hook 不触发?先查这 5 个致命点

排查项 检查方法 典型症状 解决方案
配置文件路径错误 ls -la ~/.claude/settings.jsonls -la .claude/settings.json 修改配置后无反应 确认你编辑的是 Claude 正在读取的文件(项目级 .claude/settings.json 优先级高于全局 ~/.claude/settings.json
JSON 语法错误 cat ~/.claude/settings.json | python3 -m json.tool Claude 启动时报错 Failed to load hooks config 用在线 JSON 校验器(如 jsonlint.com)粘贴内容,修复逗号、引号、括号
命令路径错误 在终端直接运行 python3 ~/.claude/my_hook.py transcript 显示 command not found 确保 python3 在 PATH 中(which python3),或用绝对路径 /usr/bin/python3
文件权限缺失 ls -l ~/.claude/my_hook.py Permission denied 错误 Linux/macOS:chmod +x ~/.claude/my_hook.py;Windows WSL 同样需要
事件未被 Claude 执行 Ctrl+O 查 transcript,搜索 PostToolUse Hook 配置正确但无日志 确认你触发的是 Write 工具(如 /write file.py),而非 AskSearch 等非工具动作

提示:在所有 Hook 脚本开头加一行日志:print(f"[DEBUG] Hook started at {datetime.now()}"),并重定向到文件 >> /tmp/hook_debug.log 2>&1。这是定位“脚本是否被执行”的黄金法则。

4.2 Hook 执行了但效果不对?聚焦输入与输出

Hook 的灵魂是 JSON 输入和 exit code 输出。90% 的逻辑错误源于对这两者的误解。

输入陷阱:

  • file_path 字段在 PreToolUsePostToolUse 中含义不同:PreToolUse 里是“计划写入的路径”,PostToolUse 里是“实际写入的路径”(可能因权限问题写入失败)。永远用 PostToolUsefile_path 做后续操作。
  • tool_responsePostToolUse 中才存在,且结构可能变化。Write 工具的响应是 {"filePath": "...", "success": true},而 RunCommand 的响应是 {"output": "...", "exitCode": 0}必须用 input_data.get("tool_response", {}) 安全获取。

输出陷阱:

  • exit code 0:成功,stdout 内容显示在 transcript。
  • exit code 2阻断性错误stderr 内容直接发给 Claude,它会据此生成解释性回复。这是 Hook 最强大的能力。
  • exit code 1:非阻断错误,stderr 显示在 transcript,但 Claude 继续执行。别用 1 做安全拦截!
  • exit code 3:延迟执行,极少用。官方文档说“effects postponed”,但实际场景模糊,建议新手忽略。

实操心得:我在做 API Key 扫描 Hook 时,曾用 exit 1 报告发现密钥,结果 Claude 完全无视,继续写入文件。改成 exit 2 后,它立刻停止并说:“检测到潜在 API 密钥,已阻止写入。建议使用环境变量管理密钥。”——exit code 是你和 Claude 之间的协议语言,错一个数字,沟通就失效。

4.3 跨平台兼容性:Windows/macOS/Linux 的 3 个生死差异

差异点 macOS/Linux Windows (原生) Windows (WSL) 解决方案
语音通知 say "text" [console]::beep()PowerShell -c "Add-Type -AssemblyName System.Speech; $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer; $speak.Speak('text')" notify-sendgdbus 在脚本中用 sys.platform 判断:if sys.platform == "darwin": ... elif sys.platform.startswith("win"): ...
路径分隔符 / \ / (WSL) 永远用 os.path.join() 构建路径,不要硬编码 /\
Shell 命令 bash cmd.exePowerShell bash Hook 的 command 字段必须调用解释器"command": "python3 script.py" 而非 "command": "script.py"

注意:Windows 原生用户,若 PowerShell 脚本报错 ExecutionPolicy,需在管理员 PowerShell 中运行 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser。这是系统级限制,Hook 无法绕过。

4.4 性能与稳定性:避免让 Hook 成为 Claude 的拖油瓶

Hook 是同步执行的,意味着 Claude 会等你的脚本跑完才继续。一个慢脚本会让整个 AI 协作卡顿。

性能优化铁律:

  • 超时必设:所有 subprocess.run() 必须加 timeout=30 参数。black . 在百万行项目里可能跑 5 分钟,必须中断。
  • I/O 必缓存:不要在 Hook 里频繁读写大文件。如需日志,用 print("log") >> /tmp/hook.log(追加模式),而非 open().write()(每次打开文件)。
  • 网络请求必降级:调用 Claude API 时,加 timeout=10,并准备 fallback(如 exit 0 静默通过)。网络抖动不该阻塞本地开发。

稳定性加固:

  • 所有外部命令用 shutil.which() 检查是否存在
    PYTHON
    import shutil
    if not shutil.which("black"):
    print("⚠️ black not found, skipping format", file=sys.stderr)
    sys.exit(0)
  • JSON 解析必 try/catch:Claude 的输入格式理论上稳定,但网络传输可能损坏。try: json.load() ... except: sys.exit(0) 是底线。

我的生产 Hook 脚本里,import 之后第一行永远是 import signal; signal.alarm(30)(Linux/macOS),为整个脚本设超时。Windows 用 threading.Timer 模拟。这是防止 Hook “挂起” Claude 的最后保险。

5. 进阶实战:从单点自动化到团队级 AI 协作中枢

当你熟练掌握基础 Hook,下一步就是构建一个可复用、可共享、可审计的 AI 协作中枢。这不是功能堆砌,而是用 Hook 重构团队工作流。

5.1 团队标准化:用 SessionStart Hook 自动注入项目上下文

新成员加入项目,第一件事是读文档。但文档常过期,而 Hook 配置是实时生效的“活文档”。

创建 ~/.claude/team_context.py

PYTHON
# !/usr/bin/env python3
import json
import sys
import os
 
input_data = json.load(sys.stdin)
cwd = input_data.get("cwd", os.getcwd())
 
# 读取项目根目录下的 CONTEXT.md(团队维护的最新规范)
context_file = os.path.join(cwd, "CONTEXT.md")
if os.path.exists(context_file):
with open(context_file, 'r') as f:
context = f.read()[:2000] # 限制长度,避免超 token
# 将上下文注入 Claude 的 system prompt(需 Claude 支持,此处模拟为 transcript 日志)
print(f"📚 Project context loaded from CONTEXT.md:\n{context[:200]}...")
else:
print("ℹ️ No CONTEXT.md found. Using default project context.")
sys.exit(0)

配置 SessionStart Hook:

JSON
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/team_context.py"
}
]
}
]
}
}

团队实践:

  • 在每个项目根目录放 CONTEXT.md,内容包括:“本项目使用 Python 3.11+,Django 4.2,API 响应必须遵循 OpenAPI 3.0 规范,所有 PR 需包含单元测试覆盖。”
  • 新成员克隆代码后,首次启动 Claude Code,SessionStart Hook 自动读取并“告诉” Claude 这些规则。
  • 效果:不再需要新人问“这个项目用什么框架?”,Claude 主动按规则生成代码。

5.2 安全合规闭环:API Key 扫描 + 自动脱敏 + 审计日志

这是金融、医疗等强监管行业的刚需。流程:PreToolUse 扫描内容 → 发现密钥 → exit 2 阻断 → Claude 建议用 Vault 替代 → PostToolUse 将事件写入审计日志。

扫描脚本核心逻辑(简化版):

PYTHON
# 在 PreToolUse 中
content = tool_input.get("content", "")
# 用正则扫描(示例:AWS Access Key)
if re.search(r"AKIA[0-9A-Z]{16}", content):
# 生成 masked key: AKIA...XYZ123
masked = re.sub(r"(