【Claude Code理论篇】Hooks 事件驱动自动化钩子:settings.json 配置骨架与触发验证

发布时间:2026/9/28 19:26:15
【Claude Code理论篇】Hooks 事件驱动自动化钩子:settings.json 配置骨架与触发验证 1. 为什么你的 Claude Code 需要 Hooks如果你已经在用 Claude Code 写代码大概率遇到过这几个场景Claude 改完一个 TypeScript 文件你手动跑一遍 ESLint 才发现它引入了一个未使用的变量Claude 准备往.env里写东西你眼疾手快按了 Esc一个长任务跑完你盯着终端等它结束就为了确认有没有报错。这些问题的共同点是它们不是对话能解决的。你可以在 CLAUDE.md 里写改完文件记得跑 lint但 Claude 不一定每次都照做你可以每次手动检查但你不可能永远记得。真正需要的是——当某个事件发生时自动触发一段你写好的逻辑。这就是 Claude Code Hooks 要干的事。Hooks 是 Claude Code 的事件驱动自动化机制当 Claude Code 内部发生特定事件调用工具、发送通知、结束回复等时自动执行你指定的 shell 命令。它和 Git 的 pre-commit / post-commit 钩子思路一致只是把触发对象从 Git 操作换成了 Claude Code 的操作。更关键的是Hook 的退出码可以反向影响 Claude 的行为——返回非零退出码时Claude 的操作会被拦截它会收到你的反馈并调整后续动作。这不是被动日志而是主动的流程控制。这篇是理论篇重点放在 settings.json 的配置骨架、事件触发链路和逐条验证方法上。我会给出可直接复制的配置片段以及每个 Hook 怎么确认它真的被触发了。适合已经用过 Claude Code、想进一步做自动化的开发者。2. TaoToken 前置把模型接入层先跑通在配 Hooks 之前得先确保 Claude Code 本身能正常跑起来。Claude Code 需要一个可用的模型接入端点TaoToken 提供的就是这一层——它兼容 Anthropic 的接口协议你拿到 API Key 后填进环境变量即可。官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 地址不带 UTMhttps://taotoken.net/api具体操作分两步。第一步去控制台创建 API Key控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keys 管理页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite第二步把 Key 写进环境变量。Claude Code 读取的是ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL这两个变量export ANTHROPIC_API_KEY你的_taotoken_key export ANTHROPIC_BASE_URLhttps://taotoken.net/api如果你用的是 zsh把这两行加到~/.zshrcbash 就加到~/.bashrc。加完执行source ~/.zshrc让它生效。验证接入是否正常可以直接跑一次模型对话模型对话入口https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite能正常返回内容说明接入层没问题接下来配 Hooks 才有意义。如果你打算长期用 Claude Code 做编码和 Agent 任务Coding Plan 会更划算Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite3. settings.json 配置骨架从零搭一个可用的 HookHooks 的配置全部写在settings.json里有两个位置可选位置作用域适合放什么~/.claude/settings.json全局所有项目生效安全类规则如敏感文件保护项目根目录.claude/settings.json仅当前项目生效项目特定规则如跑某个 linter两个位置都配了同一类 Hook 时两边都会执行不会互相覆盖。安全类的建议放全局项目相关的放项目级。3.1 最小配置结构一个完整的 Hook 配置长这样{ hooks: { PreToolUse: [ { matcher: Edit, hooks: [ { type: command, command: bash ~/.claude/hooks/guard-sensitive.sh } ] } ] } }层级看起来深其实只回答三个问题外层 key 是什么时候触发这里是 PreToolUseClaude 动手之前matcher是对哪个工具触发这里是 Edit只在编辑文件时command是触发后执行什么跑你写的脚本。matcher可以不写不写表示不管 Claude 用什么工具都触发。3.2 五种事件与触发链路Claude Code 目前支持五种 Hook 事件它们不是平级的事件触发时机典型用途PreToolUseClaude 调用工具之前拦截危险操作、校验参数PostToolUseClaude 调用工具之后自动 lint、格式化、测试NotificationClaude 发送通知时转发到 Slack、桌面通知StopClaude 结束回复时自动化收尾工作SubagentStop子代理结束时监控子任务完成状态PreToolUse 和 PostToolUse 围绕工具执行这条主线是最精细的控制点。Claude Code 的大部分操作——编辑文件用 Edit 工具、写新文件用 Write 工具、跑命令用 Bash 工具——都是工具调用你可以在这些操作前后精确插入逻辑。Notification 和 Stop 则是独立于具体工具的全局事件适合做收尾和通知。3.3 脚本的输入与输出Hook 触发时Claude Code 会往你的脚本 stdin 输入一段 JSON告诉你我正在做什么。比如 Claude 要编辑文件你收到的数据大概是这样{ tool_name: Edit, tool_input: { file_path: /path/to/file.ts, old_string: ..., new_string: ... } }你的脚本从这段 JSON 里提取信息比如取出file_path判断是不是敏感文件然后通过两种方式给 Claude 反馈。第一种是打印文字脚本里echo出来的内容会被 Claude 看到。第二种是退出码这是最关键的控制信号退出码PreToolUse动手前PostToolUse做完后0允许操作反馈给 Claude非 0阻止操作文件不会被改告诉 Claude 有问题但操作已执行注意这个区别PreToolUse 的非零退出码会真正阻止操作文件不会被修改PostToolUse 的非零退出码只是告诉 Claude后处理发现了问题因为操作已经执行完了。4. 可复制配置三个实战 Hook 片段下面给三个能直接用的配置覆盖拦截、后处理和通知三类场景。4.1 拦截敏感文件写入在全局~/.claude/settings.json里配一个 PreToolUse拦截对.env、.pem等敏感文件的编辑{ hooks: { PreToolUse: [ { matcher: Edit|Write, hooks: [ { type: command, command: bash ~/.claude/hooks/guard-sensitive.sh } ] } ] } }对应的~/.claude/hooks/guard-sensitive.sh#!/usr/bin/env bash set -euo pipefail input$(cat) file_path$(echo $input | jq -r .tool_input.file_path // empty) if [[ -z $file_path ]]; then exit 0 fi case $file_path in *.env|*.env.*|*.pem|*.key|*id_rsa*) echo 拦截$file_path 属于敏感文件不允许自动修改。请人工确认。 exit 2 ;; *) exit 0 ;; esacmatcher用了Edit|Write表示编辑和写文件都触发。退出码用 2 而不是 1是因为 Claude Code 对非零退出码都会拦截但 2 在部分版本里会作为阻塞性错误处理反馈更明确。4.2 编辑后自动跑 lint在项目级.claude/settings.json里配 PostToolUseClaude 改完.ts文件后自动跑 ESLint{ hooks: { PostToolUse: [ { matcher: Edit|Write, hooks: [ { type: command, command: bash .claude/hooks/lint-changed.sh } ] } ] } }.claude/hooks/lint-changed.sh#!/usr/bin/env bash set -euo pipefail input$(cat) file_path$(echo $input | jq -r .tool_input.file_path // empty) if [[ $file_path ! *.ts $file_path ! *.tsx ]]; then exit 0 fi if [[ ! -f $file_path ]]; then exit 0 fi if ! npx eslint $file_path 21; then echo ESLint 在 $file_path 上发现问题请修复后再继续。 exit 1 fi exit 0这里退出码用 1因为 PostToolUse 阶段文件已经改完了非零退出码的作用是让 Claude 知道后处理有问题它会读到 ESLint 的输出并尝试修复。4.3 任务结束发通知Stop 事件在 Claude 结束回复时触发适合做收尾通知{ hooks: { Stop: [ { hooks: [ { type: command, command: bash ~/.claude/hooks/notify-done.sh } ] } ] } }~/.claude/hooks/notify-done.sh#!/usr/bin/env bash set -euo pipefail input$(cat) session_id$(echo $input | jq -r .session_id // unknown) # macOS 桌面通知 if command -v osascript /dev/null 21; then osascript -e display notification \会话 $session_id 已完成\ with title \Claude Code\ fi # Linux 桌面通知 if command -v notify-send /dev/null 21; then notify-send Claude Code 会话 $session_id 已完成 fi exit 0Stop 事件的 Hook 不需要matcher因为它不针对具体工具。5. 逐条触发验证确认 Hook 真的跑了配好不等于生效。下面给每个 Hook 的验证动作确保触发链路是通的。5.1 先手动模拟脚本在写进配置之前先用管道模拟一次输入确认脚本能正确解析 JSON、返回正确退出码echo {tool_name:Edit,tool_input:{file_path:/tmp/test.env}} | bash ~/.claude/hooks/guard-sensitive.sh echo 退出码: $?预期输出是拦截信息退出码为 2。如果退出码是 0说明case匹配没生效检查file_path的提取是否正确。5.2 验证 PreToolUse 拦截在 Claude Code 里让它编辑一个.env文件比如帮我在 .env 里加一行 DEBUGtrue如果 Hook 生效Claude 会收到拦截反馈不会真的修改文件。你可以用cat .env确认内容没变。如果文件被改了说明 Hook 没触发——检查settings.json的 JSON 格式是否合法用jq . ~/.claude/settings.json验证以及matcher是否写对。5.3 验证 PostToolUse 后处理让 Claude 改一个.ts文件故意引入一个未使用变量在 src/utils.ts 里加一个 const unused 1;如果 lint Hook 生效Claude 会在编辑后收到 ESLint 的报错并尝试修复。你可以在 Claude 的回复里看到它引用了 ESLint 的输出。如果没反应先确认npx eslint在项目里能跑通再检查脚本里的文件路径判断。5.4 验证 Stop 通知随便让 Claude 完成一个小任务比如列出当前目录的文件。任务结束后桌面应该弹出通知。如果没弹检查osascript或notify-send是否可用以及脚本是否有执行权限chmod x。5.5 用日志确认触发如果以上都不确定可以在脚本开头加一行日志echo $(date) Hook triggered: $0 /tmp/claude-hooks.log跑几次操作后cat /tmp/claude-hooks.log能看到记录就说明 Hook 被调用了问题出在脚本逻辑而不是配置。6. 常见错排查Hook 不触发怎么办配 Hooks 最容易踩的坑集中在几个地方按这个顺序排查基本能定位。JSON 格式错误。settings.json对格式很严格多一个逗号、少一个引号都会导致整个文件被忽略。用jq . ~/.claude/settings.json验证报错就说明格式有问题。注意hooks是顶层 key不要嵌套错位置。matcher 写错。matcher匹配的是工具名不是文件名。Edit 工具对应EditWrite 对应WriteBash 对应Bash。想匹配多个用|分隔比如Edit|Write。写成*.ts是无效的它不会按文件扩展名匹配。脚本没有执行权限。用bash script.sh调用时不需要执行权限但如果你在command里直接写脚本路径不带bash就需要chmod x。建议统一用bash /path/to/script.sh的形式避免权限问题。jq 没装。几乎所有 Hook 脚本都依赖 jq 解析 JSON。Ubuntu 用sudo apt install jqmacOS 用brew install jq。脚本里jq命令找不到时set -e会让脚本直接退出表现为 Hook 没反应。执行时间过长。Hook 是同步执行的跑完之前 Claude 会一直等。一个 3 秒的 lint 没问题30 秒的全量测试就不合适了。长时间检查应该放到 CIHook 里只做快速校验。如果发现 Claude 响应变慢先检查 Hook 脚本的执行时间。全局和项目配置冲突。两边都配了同一事件时都会执行不会冲突但如果你只想让项目级生效记得全局那份要删掉或改条件。安全类 Hook 建议放全局项目特定的放项目级。退出码理解错。PreToolUse 返回非零会阻止操作PostToolUse 返回非零不会撤销已执行的操作只是给 Claude 反馈。如果你在 PostToolUse 里期望拦截那是做不到的得用 PreToolUse。排查完还是不行可以去接入文档对照配置示例接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite如果你在用 Claude Code 做长期编码或 Agent 任务Hooks 配合 Coding Plan 能把重复性监督真正交出去Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite配 Hooks 的顺序建议是先手动模拟脚本确认逻辑再写进 settings.json然后用日志确认触发最后才依赖它做拦截。跳过手动验证直接配出问题时你分不清是配置错还是脚本错。