Claude Code Hooks 实战指南
Claude Code Hooks 实战指南
规则靠自觉,Hooks 靠机制。
我之前在 CLAUDE.md 里写了一堆"请确保代码通过 lint"、“不要在根目录创建临时文件”,结果 AI 偶尔还是会忘。后来发现 Hooks 能解决这个问题——它是强制执行的,不是写在那等 AI 自觉遵守的。
这篇文章分享我实际在用的 Hooks 配置,以及官方支持的事件和配置方法。
一、Hooks 是什么
Hooks 就是在 Claude Code 工作流程的关键节点,自动执行你预设的脚本。不需要 AI 配合,不靠提示词,系统层面硬性执行。
SessionStart ──▶ UserPromptSubmit ──▶ PreToolUse ──▶ [工具执行] ──▶ PostToolUse
│
PreCompact ◀── 上下文压缩 │
SessionEnd ◀── 会话结束 │
Stop ◀── AI 停止输出
核心区别:
| 方式 | 执行保证 | 适用场景 |
|---|---|---|
| CLAUDE.md 规则 | AI 可能忘 | 项目约定、编码风格 |
| Hooks | 强制执行 | 安全防护、自动格式化、上下文注入 |
二、配置位置和优先级
Hooks 写在 JSON 配置文件里,有四个层级:
| 位置 | 作用范围 | 能提交到仓库吗 |
|---|---|---|
~/.claude/settings.json |
所有项目 | 不能,本地专属 |
.claude/settings.json |
单个项目 | 能 |
.claude/settings.local.json |
单个项目 | 不能(gitignored) |
插件 hooks/hooks.json |
插件启用时 | 跟插件走 |
我的做法:通用安全防护放全局,项目特定规则放项目级。
三、配置结构
一个 Hook 由三层嵌套组成:
{
"hooks": {
"事件名": [
{
"matcher": "工具匹配器",
"hooks": [
{
"type": "command",
"command": "你的脚本"
}
]
}
]
}
}
matcher 匹配规则
| 写法 | 匹配方式 | 示例 |
|---|---|---|
"*" 或 "" 或不写 |
匹配所有 | 所有事件都触发 |
纯字母+数字+| |
精确匹配 | "Bash" 或 "Edit|Write" |
| 包含其他字符 | 正则匹配 | "^Notebook" 或 "mcp__memory__.*" |
退出码含义
| 退出码 | 含义 | 效果 |
|---|---|---|
0 |
成功 | 正常继续,stdout 会被解析 |
2 |
阻断 | 阻止操作执行,stderr 反馈给 AI |
| 其他 | 非阻断错误 | 记录日志,继续执行 |
脚本能收到的环境变量
| 变量 | 说明 |
|---|---|
$CLAUDE_FILE_PATH |
当前操作的文件路径(PostToolUse 时) |
$CLAUDE_PROJECT_DIR |
项目根目录 |
$CLAUDE_SESSION_ID |
当前会话 ID |
脚本通过 stdin 接收 JSON 输入,格式取决于事件类型。
四、我实际在用的 Hooks
1. 危险命令拦截(PreToolUse + Bash)
问题:AI 偶尔会生成 git push --force、rm -rf / 这种危险命令。
配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash /path/to/ocean-dock/hooks/guard_bash.sh"
}
]
}
]
}
}
脚本做的事:
- 从 stdin 读取 JSON,提取
tool_input.command - 先检查安全命令白名单(
ls、pwd、git status等直接放行) - 匹配危险模式:
rm -rf /etc、git push --force、DROP TABLE、mkfs、shutdown等 - 命中则
exit 2阻断执行
关键代码片段:
INPUT=$(cat)
CMD=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))")
# 匹配 force push
echo "$CMD" | grep -qiE 'git\s+push\s+.*(-(-force|-f)\b)' && {
echo "[GUARD] 危险: 检测到 git push --force 操作" >&2
exit 2
}
效果:AI 要执行 git push --force 时,命令直接被拦截,AI 收到错误反馈,会自动换一种方式处理。
2. 垃圾文件拦截(PreToolUse + Write/Edit)
问题:AI 偶尔会在根目录创建 test.py、debug.py,或者写 .env 文件。
配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash /path/to/ocean-dock/hooks/guard_write.sh"
}
]
}
]
}
}
拦截规则:
__pycache__/、.pyc、.pyo→ 编译缓存.DS_Store→ 系统垃圾.git/内部文件 → 破坏仓库.egg-info/→ 打包缓存- 根目录的
test.py、tmp.py、debug.py→ 必须放tests/
3. 自动格式化(PostToolUse + Write/Edit)
问题:AI 写完代码,格式可能不一致。
两个 Hook 叠加使用:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "bash /path/to/ocean-dock/hooks/auto_check.sh"
},
{
"type": "command",
"command": "[ \"${CLAUDE_FILE_PATH##*.}\" = \"py\" ] && ruff check --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null && ruff format \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
}
]
}
]
}
}
第一个脚本 auto_check.sh 做多语言检查:
- Python → ruff check
- JS/TS → eslint
- Go → go vet
- Rust → cargo check
- 所有文件 → 行数超过 1000 行警告
第二个内联命令专门处理 Python 文件的 ruff 格式化。
4. 会话启动注入上下文(SessionStart)
问题:每次新开 session,AI 不知道之前的对话历史。
配置:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash /path/to/ocean-dock/hooks/session_start.sh"
}
]
}
]
}
}
脚本做的事:
- 查找当前项目的历史 session 列表
- 展示最近 8 个 session(ID、时间、消息数、首条消息预览)
- 通过
systemMessage注入给 Claude - 检测项目结构是否变化,如果变了提醒 AI 更新架构概览
效果:AI 开场会说"我看到这些历史 session,需要恢复哪个的上下文吗?"
5. 会话结束自动保存摘要(Stop)
问题:session 结束后,下次无法接续。
配置:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash /path/to/ocean-dock/hooks/stop.sh"
}
]
}
]
}
}
脚本做的事:
- 调用 ocean-dock 生成 session 摘要
- 保存到
.claude/LAST_HANDOFF.md - 对比项目目录结构指纹,如果变化了标记"架构记忆需要更新"
6. 上下文压缩保护(PreCompact)
问题:对话太长时 Claude Code 会自动压缩上下文,但压缩可能丢失关键信息。
配置:
{
"hooks": {
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "bash /path/to/ocean-dock/hooks/pre_compact.sh"
}
]
}
]
}
}
脚本做的事:
- 在压缩前调用 ocean-dock 生成当前 session 摘要
- 通过
systemMessage注入回 Claude,让压缩后的对话仍然保留关键信息
7. 桌面通知(Notification)
问题:等 AI 执行长任务时,不知道它什么时候需要权限确认或已经完成。
配置:
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "bash /path/to/ocean-dock/hooks/notify.sh"
}
]
},
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "bash /path/to/ocean-dock/hooks/notify.sh"
}
]
}
]
}
}
AI 需要权限确认或任务完成空闲时,桌面弹通知。
五、项目级 Hooks 示例
上面是全局配置。对于特定项目,还可以在 .claude/hooks/ 下放项目专用的 hook 文件。
比如 ai-governance-example 项目,我配了文档同步提醒:
PostToolUse 自动化矩阵:
| 文件模式 | 触发动作 |
|---|---|
**/*.py |
ruff format + ruff check --fix |
**/*.{ts,tsx} |
prettier --write |
backend/app/api/**/*.py |
提醒更新 API 文档 |
backend/app/models/**/*.py |
提醒更新数据模型文档 |
backend/app/services/**/*.py |
提醒更新验收标准 |
**/Dockerfile 等 |
提醒更新部署文档 |
| 所有文件 | 检查 TODO/FIXME 残留 |
docs/**/*.md |
文档格式验证 |
提醒类 Hook 的写法示例:
{
"event": "PostToolUse",
"tool": "Write|Edit",
"glob": "backend/app/api/**/*.py",
"action": "API 修改提醒更新文档",
"message": "你修改了 API 文件,文档可能不同步。检查以下文档是否需要更新:\n1. docs/reference/api-spec.yaml\n2. docs/design/ 对应功能的状态标记"
}
六、所有可用事件速查
| 事件 | 触发时机 | 能阻断吗 |
|---|---|---|
SessionStart |
会话启动/恢复 | 不能 |
UserPromptSubmit |
用户提交 prompt 前 | 能 |
PreToolUse |
工具执行前 | 能(核心拦截点) |
PermissionRequest |
权限对话框弹出时 | 能 |
PostToolUse |
工具执行后 | 不能(已执行) |
PostToolBatch |
一批工具都完成后 | 能(停止循环) |
Notification |
发送通知时 | 不能 |
SubagentStart |
子 Agent 启动 | 不能 |
SubagentStop |
子 Agent 完成 | 能 |
Stop |
AI 停止输出 | 能(让它继续干活) |
PreCompact |
上下文压缩前 | 能 |
PostCompact |
上下文压缩后 | 不能 |
SessionEnd |
会话结束 | 不能 |
七、Hook 脚本编写要点
输入处理
所有脚本通过 stdin 接收 JSON。Python 解析最方便:
INPUT=$(cat)
CMD=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))")
PreToolUse 的输入示例:
{
"session_id": "abc123",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
PostToolUse 的输入多了 tool_response 和 duration_ms。
输出控制
简单拦截:exit 2 + stderr 反馈
echo "危险操作被拦截" >&2
exit 2
JSON 精细控制:exit 0 + stdout JSON
# PreToolUse 允许执行并注入上下文
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"当前环境: production"}}'
# Stop 阻止 AI 停下来,让它继续
echo '{"decision":"block","reason":"还有测试没跑完"}'
# SessionStart 注入上下文
echo '{"systemMessage":"最近变更: feat/auth-refactor 分支有未提交代码"}'
关键注意点
- 安全命令白名单:拦截脚本里记得放行只读命令(
ls、git status、echo),不然 AI 连查看目录都被拦 - 脚本要幂等:同一个 Hook 可能被多次触发,脚本要能安全重复执行
- 超时控制:默认超时 600 秒,可以通过
timeout字段调整 - stdout 只输出有效内容:shell 启动时的欢迎文本会干扰 JSON 解析
八、调试技巧
/hooks命令:在 Claude Code 里输入/hooks,能看到所有已配置的 Hook 及其来源claude --debug:debug 日志会记录每个 Hook 的匹配、执行、退出码claude --debug-file <path>:指定 debug 日志输出位置CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose:更详细的匹配过程日志
九、进阶玩法
异步 Hook
后台执行,不阻塞 AI:
{
"type": "command",
"command": "/path/to/run-tests.sh",
"async": true,
"timeout": 300
}
适合跑测试套件这种耗时操作,AI 继续工作,测试完了把结果反馈回来。
HTTP Hook
把事件发到远程服务:
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
Prompt Hook
让 AI 判断该不该放行:
{
"type": "prompt",
"prompt": "评估 Claude 是否该停止: $ARGUMENTS。检查所有任务是否完成。",
"timeout": 30
}
底层调一个快模型返回 {ok: true/false} 判断。
Agent Hook
比 Prompt Hook 更强——能启动一个子 Agent 读文件、跑命令后再判断:
{
"type": "agent",
"prompt": "验证所有单元测试是否通过。运行测试套件并检查结果。$ARGUMENTS",
"timeout": 120
}
十、总结
我用 Hooks 解决的核心问题:
| 问题 | Hook 方案 | 效果 |
|---|---|---|
| AI 忘记 lint | PostToolUse 自动 ruff format | 写完就格式化 |
| 危险命令 | PreToolUse 拦截 | force push 等直接拦掉 |
| 临时文件污染 | PreToolUse 拦截 | 根目录不让建 test.py |
| 文档过时 | PostToolUse 提醒 | 改 API 文件时弹提醒 |
| 上下文丢失 | PreCompact 注入摘要 | 压缩后关键信息还在 |
| 会话不连续 | Stop 保存 + SessionStart 恢复 | 开场就能接上 |
配置层级建议:通用防护放全局 ~/.claude/settings.json,项目特定的放 .claude/settings.json,团队共享的提交到仓库。
调试口诀:先用 /hooks 确认配置生效,再用 --debug 看执行日志。
十一、补充:.githooks(Git Hooks)
Claude Code Hooks 管 AI 的行为,Git Hooks 管人的行为(包括 AI 帮你 commit/push 时)。
两者配合形成完整防线:
┌─────────────────────────────────────────────────────────────────┐
│ 双层防护体系 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Claude Code Hooks Git Hooks (.githooks) │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ AI 写代码时 │ │ git commit 时 │ │
│ │ • 拦截危险命令 │ │ • 垃圾文件检测 │ │
│ │ • 自动格式化 │ ──▶ │ • 敏感信息扫描 │ │
│ │ • 上下文保护 │ │ • commit 格式 │ │
│ │ • 文档同步提醒 │ │ • 测试运行 │ │
│ └────────────────┘ └────────────────┘ │
│ 实时拦截 提交门禁 │
│ │
└─────────────────────────────────────────────────────────────────┘
为什么用 .githooks 而不是 .git/hooks
.git/hooks/ 不会被提交到仓库,团队成员拿不到。.githooks/ 放在项目根目录,通过 git config core.hooksPath .githooks 激活,团队共享。
激活方式
# 项目根目录执行一次即可
git config core.hooksPath .githooks
也可以在 AGENTS.md 或 CLAUDE.md 里写上这句,让 AI 在新项目初始化时自动执行。
我用的四个 Git Hook
1. pre-commit — 提交前检查
三道关卡:
# ① 垃圾文件检测
# 扫描暂存区,发现 __pycache__/、.pyc、.DS_Store、*.tmp、*.bak 直接报错
# ② 敏感信息扫描
# 正则匹配 password=、api_key=、secret=、token=、sk-xxx 模式
# ③ ruff 代码检查(如果可用)
# 只检查暂存的 Python 文件
效果示例:
[pre-commit] 运行检查...
[1/3] 检查垃圾文件...
[OK] 未检测到垃圾文件
[2/3] 检查敏感信息...
[WARNING] config.py 可能包含敏感信息 (pattern: api_key\s*=\s*["']...)
[3/3] ruff 检查...
[OK] ruff check 通过
[pre-commit] 所有检查通过
不通过时 commit 被阻止,必须修复后重试。用 git commit --no-verify 可以跳过,但显然不推荐。
2. commit-msg — 提交格式校验
检查 commit message 是否符合 Conventional Commits 格式:
feat(api): 添加用户认证接口
fix(parser): 修复空输入崩溃问题
docs: 更新 API 文档
这个 hook 只警告不阻断(exit 0),因为有时候确实需要写非标准格式的 commit message。另外检查标题长度是否超过 72 字符。
3. pre-push — 推送前跑测试
# 支持 SKIP_PRE_PUSH_TESTS=1 git push 跳过
# 如果没有 tests/ 目录或 pytest 没装也自动跳过
# pytest 退出码 5(无测试收集)视为通过
确保推到远程的代码至少本地测试是通过的。
4. post-checkout — 切换分支后清理缓存
切换分支时自动清理 __pycache__/、.pyc、.pyo,避免残留缓存文件造成干扰。
.githooks 目录结构
.githooks/
├── pre-commit # 提交前:垃圾文件 + 敏感信息 + lint
├── commit-msg # 提交信息格式校验
├── pre-push # 推送前:跑测试
└── post-checkout # 切分支后:清理缓存
和 Claude Code Hooks 的分工
| 检查项 | Claude Code Hook | Git Hook | 理由 |
|---|---|---|---|
| 危险命令拦截 | PreToolUse (Bash) | — | AI 专属场景 |
| 自动格式化 | PostToolUse (Write/Edit) | — | 实时修正 |
| 上下文保护 | PreCompact / Stop | — | AI 会话专属 |
| 垃圾文件 | PreToolUse 拦截写入 | pre-commit 拦截提交 | 双保险 |
| 敏感信息 | — | pre-commit 扫描 | 提交是最后防线 |
| commit 格式 | — | commit-msg | Git 专属 |
| 测试 | — | pre-push | 推送是最后防线 |
核心原则:本地 Hook 是开发反馈,CI 是合并门——两层独立,本地没启用 Hook 不会绕过 CI 检查。
参考链接
- Hooks 官方参考文档 — 事件、配置、JSON 格式完整说明
- Hooks 入门指南 — 官方手把手教程
- Claude Code Hooks 生产级实践 — 三种 Hook 类型深度对比
- 12 个生产级 Hook 配置 — 实用配置合集
更多推荐


所有评论(0)