前言:为什么前端开发者对 Hooks 如此熟悉
在编写 Vue 组件时,我们通常在 onMounted 中发送请求,在 onBeforeUnmount 里清理定时器。这种事件驱动的编程思维,前端开发者早已驾轻就熟。Claude Code 将整个 AI 协作流程拆解为 25 个生命周期事件:用户输入、调用工具前后、会话启动与结束……每个关键时刻都可以挂载一个钩子(Hook)。
这套机制究竟能带来什么?
- 强制 Claude 完成代码编写后自动运行 Prettier——无需再反复提醒“请记得格式化”
- 拦截危险指令——例如误操作执行
rm -rf,钩子能直接阻止 - 追踪上下文消耗——每次请求消耗的 token 数量清晰可见
- 统一团队规范——规则写入
.claude/settings.json,提交至 Git 后全员共享
接下来,我们将从配置层级入手,逐步深入理解这套机制。
一、配置层级:与前端的工具链异曲同工
前端开发者对 .eslintrc 的层级覆盖并不陌生:项目级配置会覆盖用户级,当前目录配置会覆盖父目录。Claude Code 的 Hooks 遵循了相同的逻辑。
| 配置位置 | 作用范围 | 类比前端 |
|---|---|---|
~/.claude/settings.json |
用户全局 | 全局 .npmrc |
.claude/settings.json |
项目共享(提交至 Git) | 项目根 .eslintrc |
.claude/settings.local.json |
本地私有(不提交) | .env.local |
Plugin hooks/hooks.json |
插件内部 | 包内置规则 |
| Skill / Agent frontmatter | 组件级 | 组件 scoped 样式 |
最佳实践非常清晰:将团队共享规则放在 .claude/settings.json 中,个人偏好则保留在 settings.local.json 里。
基础配置结构
{"hooks": {"EventName": [{"matcher": "ToolPattern","hooks": [{"type": "command","command": "your-command-here","timeout": 60}]}]}}
几个关键字段的含义如下:
matcher:工具名称匹配模式,支持精确匹配如"Write"、正则如"Edit|Write"、通配符"*",甚至可匹配 MCP 工具的"mcp__memory__.*"type:四种类型——command(执行 Shell 命令)、http(调用远程 Webhook)、prompt(让 LLM 进行评估)、agent(派生子 Agent 进行验证)timeout:超时设置,默认值为 60 秒once:是否在单次会话中仅运行一次
二、四种钩子类型:从命令执行到 AI 评估
1. Command Hook:最常用的 Shell 命令
这与 Husky 的 pre-commit 钩子原理相同,本质就是执行一段脚本:
{"type": "command","command": "python3 "$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py"","timeout": 60}
脚本通过标准输入(stdin)接收 JSON 数据,并通过退出码(exit code)反馈执行结果:
- exit 0:通过验证,流程继续
- exit 2:阻断操作,并将标准错误(stderr)的内容作为提示信息
- 其他值:非阻断性错误,仅在 verbose 模式下可见
2. HTTP Hook:调用远程 Webhook
这是 v2.1.63 版本新增的功能,非常适合接入团队的 CI/CD 系统或企业内部审计平台:
{"type": "http","url": "https://my-webhook.example.com/hook","matcher": "Write"}
一个贴近前端开发的实际案例:代码提交后自动向钉钉群发送通知。
前端团队每次合并代码时,都希望钉钉机器人能自动推送一条消息到群内。传统做法是配置 Git Webhook,但现在可以通过 HTTP Hook 在 PostToolUse 事件触发时实现:
{"hooks": {"PostToolUse": [{"matcher": "Bash","hooks": [{"type": "http","url": "https://oapi.dingtalk.com/robot/send?access_token=${WEBHOOK_TOKEN}","allowedEnvVars": ["WEBHOOK_TOKEN"],"timeout": 10}]}]}}
这个钩子在每次 Bash 命令执行后(例如 git commit)自动向钉钉机器人发送 POST 请求。请求体包含 Hook 接收到的 JSON 数据(如 session_id、tool_name、cwd 等),钉钉端可以据此判断谁执行了哪些操作。
另一个应用场景:在文件写入后自动同步到 CDN 回滚系统。
{"hooks": {"PostToolUse": [{"matcher": "Write|Edit","hooks": [{"type": "http","url": "https://cdn-rollback.internal.example.com/api/snapshot?env=${DEPLOY_ENV}","allowedEnvVars": ["DEPLOY_ENV"],"timeout": 30}]}]}}
当 AI 完成文件写入后,系统会自动将文件路径和内容快照发送至内部的 CDN 回滚服务,这样后续若出现问题可以快速恢复。
调试技巧:可以利用 httpbin.org 进行本地测试。
# 使用 httpbin.org 测试 webhook 格式(它会原样返回请求体)
# 配置 "url": "https://httpbin.org/post"
# 观察 Claude 传递过来的 JSON 结构
3. Prompt Hook:让 LLM 充当裁判
该类型主要用于 Stop / SubagentStop 事件,让 AI 自行评估“任务是否完成”:
{"type": "prompt","prompt": "Evaluate if Claude completed all requested tasks.","timeout": 30}
LLM 会返回结构化的决策结果:approve(通过)或 block(阻断),并附带理由说明。对于“需求是否真正实现”这类主观判断,Prompt Hook 比编写正则表达式要可靠得多。
4. Agent Hook:派生子 Agent 进行多步验证
这种类型的钩子功能比 Prompt Hook 更加强大:它会派发一个能够使用工具(如 Read、Grep、Bash)的子 Agent,来执行复杂的检查任务。例如,对照设计文档验证架构是否符合规范:
{"type": "agent","prompt": "Verify the code changes follow our architecture guidelines.","timeout": 120}
三、25 个事件:前端开发者最关注的部分
完整的事件表涵盖了从会话启动到工具调用的整个生命周期。对于前端开发者而言,最实用的几个事件如下:
| 事件 | 触发时机 | 能否阻断 | 前端典型用途 |
|---|---|---|---|
| PreToolUse | 工具执行前 | ✅ allow/deny/ask | 拦截危险命令、改写参数 |
| PostToolUse | 工具执行成功后 | ❌ | 自动 lint/格式化、Bundle 校验 |
| PostToolUseFailure | 工具执行失败 | ❌ | 错误埋点、Sentry 上报 |
| UserPromptSubmit | 用户提交输入 | ✅ | 校验、注入项目上下文 |
| Stop | Claude 回答结束 | ✅ | 任务完整性检查 |
| SessionStart | 会话启动 | ❌ | 加载环境变量、激活 nvm |
| FileChanged | 监听文件变化 | ❌ | 触发 HMR、重启 dev server |
PreToolUse:你的“防火墙”
这个事件类似于 Vue Router 的 beforeEach 导航守卫:可以拦截、放行,甚至改写操作。
{"hooks": {"PreToolUse": [{"matcher": "Bash","hooks": [{"type": "command","command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"}]}]}}
输出可以灵活控制:
permissionDecision:"allow"(放行)/"deny"(阻止)/"ask"(询问用户)updatedInput:改写工具入参(例如自动修正文件路径)
PostToolUse:你的“自动美容师”
代码编写完成后立即进行格式化,从此无需手动执行 Prettier 命令。
四、前端开发者实战:5 个高价值的钩子场景
场景 1:写完代码自动运行 Prettier + ESLint
创建一个 .claude/hooks/format-code.sh 脚本:
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_name', ''))")
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_input', {}).get('file_path', ''))")
if [ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ]; then
exit 0
fi
case "$FILE_PATH" in
*.js|*.jsx|*.ts|*.tsx|*.vue|*.json)
command -v prettier &>/dev/null && prettier --write "$FILE_PATH" 2>/dev/null
command -v eslint &>/dev/null && eslint --fix "$FILE_PATH" 2>/dev/null
;;
*.css|*.scss|*.less)
command -v stylelint &>/dev/null && stylelint --fix "$FILE_PATH" 2>/dev/null
;;
esac
exit 0
将这个脚本挂载到 PostToolUse 事件上,此后每当 AI 完成一个 .vue 文件的编写,下一秒就会自动符合团队规范。
场景 2:阻断危险的 Bash 命令
创建一个 validate-bash.py 脚本:
#!/usr/bin/env python3
import json, sys, re
BLOCKED_PATTERNS = [
(r"brms+-rfs+/", "禁止 rm -rf 根目录"),
(r"bsudos+rm", "禁止 sudo rm"),
(r"bnpms+publish", "publish 操作必须人工确认"),
(r"bgits+pushs+.*--force", "禁止 force push"),
]
def main():
data = json.load(sys.stdin)
if data.get("tool_name") != "Bash":
sys.exit(0)
cmd = data.get("tool_input", {}).get("command", "")
for pattern, msg in BLOCKED_PATTERNS:
if re.search(pattern, cmd):
print(msg, file=sys.stderr)
sys.exit(2)
sys.exit(0)
if __name__ == "__main__":
main()
这里使用 exit 2 实现阻断操作,并在终端显示具体原因。这比撰写大量 docstring 来提醒 Claude“不要这样做”要有效得多。
场景 3:写文件时扫描敏感信息
创建一个 security-scan.py 脚本,用于检测代码中是否硬编码了 API Key 或密码:
SECRET_PATTERNS = [
(r"passwords*=s*['"][^'"]+['"]", "可能存在硬编码密码"),
(r"api[_-]?keys*=s*['"][^'"]+['"]", "可能存在硬编码 API Key"),
(r"sk-[a-zA-Z0-9]{32,}", "可能泄漏 OpenAI Key"),
]
一旦发现敏感信息,可以通过 additionalContext 将警告信息回传至 Claude 的上下文中,让它自行修正。这比依靠人工进行 Code Review 要快得多。
场景 4:Session 启动时加载 nvm 环境
前端项目通常会包含 .nvmrc 文件。在 SessionStart 钩子中配置:
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ] && [ -f ".nvmrc" ]; then
NODE_VERSION=$(cat .nvmrc)
echo "export NVM_NODE_VERSION=$NODE_VERSION" >> "$CLAUDE_ENV_FILE"
fi
exit 0
这里的 CLAUDE_ENV_FILE 是一个特殊的环境变量。写入其中的 export 语句会在整个会话中持续生效——这一点比普通的 shell 命令要强大得多。
场景 5:上下文用量追踪(Hook Pair 用法)
将 UserPromptSubmit 和 Stop 事件分别视为“请求开始”和“请求结束”,用于计算每次对话的 token 消耗量:
# 在 UserPromptSubmit 时记录当前 token 数量
# 在 Stop 时再计算一次,差值即为本轮消耗
delta_tokens = current_tokens - pre_tokens
print(f"本次请求约消耗 {delta_tokens:,} tokens", file=sys.stderr)
这与在 Vue 开发中使用 performance.mark('start') 和 performance.measure() 来测量函数执行时长的思路完全一致。
五、组件级 Hooks:将规则嵌入 Skill / Agent
如果你编写了一个 Skill,并希望它每次执行时自动进行某些校验,可以直接在 frontmatter 中配置钩子:
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/check.sh"
once: true # 在整个会话中只执行一次
---
支持的事件类型包括:PreToolUse、PostToolUse、Stop。
这里有一个特别的说明:当 Stop 钩子定义在子 Agent 的 frontmatter 中时,它会被自动转换为 SubagentStop——仅在子 Agent 完成时触发,不会影响主会话。
六、JSON I/O 与环境变量
输入(stdin JSON)
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.js",
"content": "..."
},
"tool_use_id": "toolu_01ABC123...",
"agent_id": "agent-abc123",
"agent_type": "main"
}
输出(stdout JSON, exit 0)
{
"continue": true,
"suppressOutput": false,
"systemMessage": "可选的警告消息",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "文件在允许目录内",
"updatedInput": {
"file_path": "/modified/path.js"
}
}
}
关键环境变量
| 变量 | 用途 |
|---|---|
CLAUDE_PROJECT_DIR |
项目根目录的绝对路径(务必使用此变量,避免硬编码) |
CLAUDE_ENV_FILE |
持久化环境变量的文件路径(仅适用于 SessionStart/CwdChanged/FileChanged 事件) |
CLAUDE_CODE_REMOTE |
标识是否在远程环境中运行(值为 "true") |
${CLAUDE_PLUGIN_ROOT} |
插件目录路径(仅适用于插件内部的 hooks) |
七、安全最佳实践:避免给自己制造风险
Hooks 可以执行任意 shell 命令,这与 Husky 一样存在安全风险。以下是一份安全对照清单:
| ✅ 应该做 | ❌ 不要做 |
|---|---|
| 校验并清洗所有输入数据 | 盲目信任输入来源 |
使用引号包裹变量 "$VAR" |
直接裸用 $VAR(可能导致命令注入) |
禁止路径穿越(如 ..) |
接受任意路径参数 |
使用 $CLAUDE_PROJECT_DIR |
硬编码绝对路径 |
跳过 .env、.git/、密钥文件等 |
处理所有文件 |
| 在隔离环境中先行测试 | 直接部署到生产环境 |
HTTP hook 中显式声明 allowedEnvVars |
将所有环境变量不加区分地发送给外部接口 |
特别提醒:statusLine 和 fileSuggestion 类型的钩子需要工作区信任授权才能生效——这是新版中新增的安全闸门。
八、调试技巧
# 启动 debug 模式以查看完整的钩子日志
claude --debug
# 独立测试钩子(绕过 Claude 直接测试)
echo '{"tool_name": "Bash", "tool_input": {"command": "ls -la"}}' | python3 .claude/hooks/validate-bash.py
echo $? # 查看 exit code
在会话中按下 Ctrl+O 可以切换 verbose 模式,能够实时查看每个钩子的执行情况。
常见问题
- 钩子未触发:请检查 JSON 语法是否正确、matcher 拼写是否有误、脚本是否具有可执行权限(
chmod +x) - 意外被阻断:建议先用样例 JSON 数据进行本地测试,确认 exit code 符合预期
- JSON 解析报错:务必通过标准输入(stdin)读取数据,不要从命令行参数读取
九、Claude Hooks 与前端工具链对比
将所有概念串联起来,为有经验的前端工程师提供一张速查表:
| 前端世界 | Claude Hooks | 共同点 |
|---|---|---|
Husky pre-commit |
PreToolUse (Bash) |
阻断危险操作 |
Husky post-commit |
PostToolUse |
操作后处理 |
lint-staged |
PostToolUse (Write/Edit) |
修改后立即格式化 |
Webpack compiler.hooks.done |
Stop |
任务结束时的钩子 |
Vue onMounted |
SessionStart |
启动时初始化 |
Vue onBeforeUnmount |
SessionEnd |
退出时清理 |
Vue Router beforeEach |
PreToolUse (allow/deny) |
导航守卫 |
Vite handleHotUpdate |
FileChanged |
文件变更响应 |
十、写在最后:Hooks 是 AI 协作的“工程化护栏”
前端开发者最在意的,并非代码写得多么炫目,而是协作过程可控、规范统一。Hooks 赋予我们将 AI 也纳入工程化体系的能力:
- 编写代码自动遵循规范——不再需要 Code Review 反复纠正格式问题
- 危险命令直接阻断——即使 AI 出现失误也不会对项目造成损失
- 关键节点自动审计——满足合规与安全要求
- 团队规则共享——提交
.claude/settings.json即可实现统一
下次再有同事说“AI 写的代码我不放心”,请把本文分享给他——让 AI 与你的工程化体系深度打通,AI 才能真正成为值得信赖的工程师,而不仅仅是会写代码的助手。
十一、完整配置示例
以下是一个面向生产环境的完整配置,涵盖了前文提到的所有核心场景:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ""$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh"",
"timeout": 30
},
{
"type": "command",
"command": "python3 "$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py"",
"timeout": 10
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py""
}
]
}
],
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": ""$CLAUDE_PROJECT_DIR/.claude/hooks/session-init.sh""
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if Claude completed all requested tasks. Check: 1) Were all files created/modified? 2) Were there unresolved errors? If incomplete, explain what's missing.",
"timeout": 30
}
]
}
]
}
}
配置说明:
| 事件 | 匹配器 | 钩子类型 | 用途 |
|---|---|---|---|
PreToolUse |
Bash |
command | 拦截危险命令(如 rm -rf、force push 等) |
PostToolUse |
Write|Edit |
command × 2 | 自动格式化代码 + 扫描敏感信息 |
UserPromptSubmit |
- | command | 验证用户输入,阻断危险操作 |
SessionStart |
startup |
command | 加载 nvm 环境、初始化工作区 |
Stop |
- | prompt | AI 回答结束后评估任务完成度 |
配套脚本清单:
| 脚本 | 触发时机 | 功能 |
|---|---|---|
validate-bash.py |
PreToolUse/Bash | 阻断危险的 Shell 命令 |
format-code.sh |
PostToolUse | 根据文件扩展名自动运行 Prettier/ESLint/StyleLint |
security-scan.py |
PostToolUse | 扫描硬编码密码、API Key 等敏感信息 |
validate-prompt.py |
UserPromptSubmit | 验证用户输入是否包含危险操作 |
session-init.sh |
SessionStart | 加载 .nvmrc、初始化环境变量 |
部署步骤:
# 1. 创建钩子目录
mkdir -p .claude/hooks
# 2. 复制所有脚本(需自行实现上述脚本)
cp /path/to/your/hooks/* .claude/hooks/
chmod +x .claude/hooks/*.sh
chmod +x .claude/hooks/*.py
# 3. 将配置添加到项目设置
# 将上述 JSON 配置添加到 .claude/settings.json 的 hooks 字段中
# 4. 提交到 Git(实现团队共享)
git add .claude/
git commit -m "feat: 添加 Claude Code Hooks 配置"
# 5. 验证钩子是否生效
claude --debug
推荐使用场景对照表:
| 团队规模 | 推荐配置 | 说明 |
|---|---|---|
| 个人开发者 | PreToolUse + PostToolUse | 聚焦安全与格式化 |
| 小团队 (3-10人) | + SessionStart + UserPromptSubmit | 统一环境 + 输入规范 |
| 中大型团队 | 全部开启 + HTTP Hook | 接入 CI/CD、审计平台 |
| 高安全要求 | 全部开启 + Agent Hook | 多步验证、合规审计 |
参考资源
- Claude Code Hooks 官方文档
- Claude Code CLI 参考
