博客文章
Cocos 游戏项目的 Claude Code 配置实践(6):Hooks
Rules 靠 AI 自觉遵守,但 AI 有时会「忘记」规范。Hooks 在系统层面强制执行:AI 写完文件立刻跑 eslint 和 tsc 类型检查,有问题自动反馈修复。分享我的三个核心 Hook 脚本和编写技巧。
Cocos 游戏项目的 Claude Code 配置实践(6):Hooks
系列文章目录:总览 → CLAUDE.md → Rules → Skills → MCP → [Hooks] → Agents
朋友们大家好,我是 bit老宫 呀,一个拥有12年一线开发经验的CocosCreator游戏开发者,代表作 《比特小队》 《宫爆老奶奶家族篇》
前四层都是「告诉 AI 应该怎么做」——CLAUDE.md 告诉它项目背景,Rules 告诉它代码规范,Skills 告诉它执行步骤,MCP 让它查 API。但说到底,这些都靠 AI「自觉」遵守。
问题是,AI 有时候会「忘记」。尤其在长对话里,上下文越来越长,AI 可能漏掉某条规范——写了个 any 类型、忘了加访问修饰符、甚至留了个 console.log。
Hooks 不一样。它是系统层面的强制执行——无论 AI 做了什么,都会触发对应的 shell 命令。AI 写了一个文件?立刻跑类型检查。AI 完成了任务?立刻发通知。不需要 AI 配合,系统自动执行。
工作原理
Hooks 配置在项目的 .claude/settings.json 里,绑定到 Claude Code 的事件节点:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "bash .claude/hooks/git-push-reminder.sh" }]
}],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "bash .claude/hooks/eslint-fix.sh" }]
},
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "bash .claude/hooks/tsc-check.sh" }]
},
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "bash .claude/hooks/console-log-warn.sh" }]
}
],
"Stop": [{
"matcher": "*",
"hooks": [{ "type": "command", "command": "bash .claude/hooks/learning.sh" }]
}]
}
}
三个关键事件节点:
PreToolUse:AI 调用工具之前触发。可以拦截、提醒、甚至阻止执行PostToolUse:AI 调用工具之后触发。适合做检查和修复Stop:AI 完成一轮对话后触发。适合做通知和总结
matcher 指定触发条件:"Edit|Write" 表示只在编辑或写入文件时触发,"Bash" 表示只在执行命令时触发,"*" 表示所有工具调用都触发。
我的三个核心 Hooks
Hook 1:eslint-fix — 写完自动修复代码风格
#!/bin/bash
# PostToolUse hook: 编辑 .ts 文件后自动运行 eslint --fix
INPUT=$(cat)
FILEPATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# 只处理 assets/script/ 下的 .ts 文件
if [ -z "$FILEPATH" ]; then exit 0; fi
if ! echo "$FILEPATH" | grep -q '\.ts$'; then exit 0; fi
if ! echo "$FILEPATH" | grep -q '/assets/script/'; then exit 0; fi
if [ ! -f "$FILEPATH" ]; then exit 0; fi
cd "$CLAUDE_PROJECT_DIR" || exit 0
npx eslint --fix "$FILEPATH" 2>/dev/null || true
exit 0
这个 Hook 的逻辑很简单:AI 每次写入或编辑 .ts 文件后,自动跑一遍 eslint --fix。import 顺序不对?自动修。缺少分号?自动补。缩进不一致?自动修。
关键设计:只处理 assets/script/ 下的 .ts 文件。项目里还有配置文件、文档、脚本——这些不需要 eslint,精确匹配避免误伤。
Hook 2:tsc-check — 写完自动检查类型
#!/bin/bash
# PostToolUse hook: 编辑 .ts 文件后运行 TypeScript 类型检查
INPUT=$(cat)
FILEPATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
if [ -z "$FILEPATH" ]; then exit 0; fi
if ! echo "$FILEPATH" | grep -q '\.ts$'; then exit 0; fi
if ! echo "$FILEPATH" | grep -q '/assets/script/'; then exit 0; fi
cd "$CLAUDE_PROJECT_DIR" || exit 0
# 运行 tsc 类型检查,过滤引擎内部错误
ERRORS=$(npx tsc --noEmit 2>&1 | grep -E "error TS" | grep "assets/script/" | head -10)
if [ -n "$ERRORS" ]; then
echo "TypeScript 类型检查发现错误:"
echo "$ERRORS"
ERROR_COUNT=$(echo "$ERRORS" | wc -l | tr -d ' ')
if [ "$ERROR_COUNT" -ge 10 ]; then
echo "(仅显示前 10 条,可能还有更多)"
fi
fi
exit 0
和 eslint Hook 互补:eslint 管代码风格,tsc 管类型安全。
AI 写了一个文件,tsc 立刻全量检查。如果有类型错误,错误信息会反馈给 AI,AI 会自动修复——整个过程不需要人工介入。你经常会看到这样的流程:
- AI 写了一段代码
- tsc Hook 检查发现类型错误
- AI 收到错误信息,自动修改代码
- tsc 再次检查,通过
这就是 Hook 的价值:不是「告诉 AI 要注意类型」(Rules 已经告诉了),而是「写完自动检查,有问题立刻修」。
几个设计细节:
- 过滤引擎内部错误:
grep "assets/script/"只显示项目代码的错误,忽略引擎 .d.ts 里的告警 - 限制输出条数:
head -10避免大量错误刷屏,AI 也不需要一次看到所有错误 - 不阻塞执行:Hook 的 exit code 始终是 0,不会阻止 AI 继续工作
Hook 3:console-log-warn — 检查遗留的调试代码
#!/bin/bash
# PostToolUse hook: 检查修改的 .ts 文件中是否包含 console.log
INPUT=$(cat)
FILEPATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
if [ -z "$FILEPATH" ]; then exit 0; fi
if ! echo "$FILEPATH" | grep -q '\.ts$'; then exit 0; fi
if ! echo "$FILEPATH" | grep -q '/assets/script/'; then exit 0; fi
if [ ! -f "$FILEPATH" ]; then exit 0; fi
MATCHES=$(grep -n 'console\.log' "$FILEPATH" | grep -v '//.*console\.log' | head -5)
if [ -n "$MATCHES" ]; then
echo "警告:文件中存在 console.log,发布前请清理:"
echo "$MATCHES"
fi
exit 0
我们项目里禁止使用 console.log,必须用框架的 CORE.log()。Rules 里已经写了这条规范,但 AI 偶尔还是会写出 console.log。这个 Hook 是最后一道防线——写了就提醒,AI 看到提醒就会自动替换。
Hook 脚本编写技巧
从 stdin 读取的 JSON 包含工具调用的完整信息:
{
"tool_name": "Edit",
"tool_input": {
"file_path": "/path/to/file.ts",
"old_string": "...",
"new_string": "..."
}
}
几个通用技巧:
- 用
jq提取字段:echo "$INPUT" | jq -r '.tool_input.file_path // empty' - 精确匹配文件类型和路径:避免对不相关的文件触发检查
- 始终 exit 0:除非你确实想阻止 AI 继续操作,否则 Hook 不应该失败
- 限制输出量:太多输出会干扰 AI 的判断,
head限制一下
用户级 Hooks vs 项目级 Hooks
Claude Code 的 Hooks 支持两个层级:
- 项目级(
.claude/settings.json):跟随项目仓库,团队共享 - 用户级(
~/.claude/settings.json):只对当前用户生效
我的做法是:
| Hook | 层级 | 理由 |
|---|---|---|
| eslint-fix、tsc-check、console-log-warn | 项目级 | 代码质量要求全团队一致 |
| Stop 通知(桌面通知) | 用户级 | 个人偏好,不强制团队 |
桌面通知的 Hook 是我个人加的——AI 完成长任务后弹一条系统通知,不用一直盯着终端:
{
"Stop": [{
"hooks": [{
"type": "command",
"command": "osascript -e 'display notification \"任务完成\" with title \"Claude Code\"'"
}]
}]
}
和 Rules 的协作模式
Rules 和 Hooks 不是二选一,而是互补:
| Rules | Hooks | |
|---|---|---|
| 机制 | 告知 AI 规范 | 系统强制执行 |
| 时机 | AI 写代码之前 | AI 写代码之后 |
| 效果 | AI「知道」要遵守 | AI「必须」遵守 |
| 覆盖面 | 广泛(任何规范) | 有限(只能检查可自动化的项) |
最佳实践:Rules 做第一道防线(让 AI 尽量一次写对),Hooks 做兜底(万一写错了自动修)。
踩坑经验
坑一:tsc 检查太慢
npx tsc --noEmit 对大项目来说可能要 5-10 秒。每次 AI 编辑一个文件就跑一遍,如果连续编辑 10 个文件,光类型检查就要等一两分钟。
我的处理:目前能接受这个等待时间,因为类型安全比速度更重要。如果实在太慢,可以考虑用 tsc --incremental 增量检查,或者只在 AI 完成一组编辑后手动触发。
坑二:Hook 脚本权限问题
新建的 .sh 文件默认没有执行权限,Hook 会静默失败。记得 chmod +x 所有 Hook 脚本。
坑三:不同工具的输入字段名不同
Edit 工具用 file_path,但有些工具可能用 filePath(驼峰式)。我的 tsc Hook 里做了兼容:jq -r '.tool_input.file_path // .tool_input.filePath // empty'——两种写法都试一遍。
下一篇
Hooks 在系统层面保障了代码质量。但有些任务不是「检查对不对」的问题,而是需要深度分析和专业判断——比如代码 review、架构合规检查。下一篇聊最后一层:Agents——给这类复杂任务配备专属专家。