1. Claude Code Hook 生命周期到底解决什么问题
Claude Code 的 Hook 机制,说白了就是给会话过程装上一排「自动感应开关」。你在本地开发时,Claude Code 会经历会话启动、提交提示词、调用工具、完成任务、会话结束等一系列节点,Hook 允许你在这些节点上挂载自定义的 shell 命令、HTTP 端点或 LLM 提示,让它们在特定时机自动跑起来。它适合谁?适合那些希望把代码规范校验、危险命令拦截、环境变量注入、日志审计等动作自动化,而不是每次手动盯着的开发者。
我最初接触 Hook 是因为一个很具体的痛点:Claude Code 在帮我重构代码时,偶尔会执行rm -rf之类的破坏性命令,或者提交前忘记跑 lint。手动检查太累,于是开始研究 Hook 的触发时机和生命周期链路。理解生命周期是配置 Hook 的前提——你得知道事件在什么时候触发、输入从哪来、输出怎么影响后续流程,否则配置写了也不生效。
Claude Code Hook 的生命周期可以按触发节奏分成三类:每次会话一次的事件(SessionStart、SessionEnd)、每次回合一次的事件(UserPromptSubmit、Stop、StopFailure),以及代理循环内每次工具调用都会触发的事件(PreToolUse、PostToolUse、PostToolUseFailure)。这个分类很关键,因为它决定了你的 Hook 脚本会被执行多少次、拿到什么样的上下文。
举个例子,PreToolUse 在工具调用执行之前触发,你可以在这里检查即将执行的命令并决定是否阻止;PostToolUse 在工具调用成功后触发,适合做结果校验或日志记录。两者配合,就能实现「执行前拦截 + 执行后审计」的完整闭环。理解这些事件的先后顺序和输入输出格式,是写出可靠 Hook 配置的基础。接下来的章节会从环境准备开始,一步步带你完成配置、验证和排障。
2. TaoToken 统一 Key 通道的前置准备
在配置 Hook 之前,有一个容易被忽略但很关键的前置环节:调用凭证的管理。Claude Code 在运行过程中会频繁调用模型 API,如果你的 Hook 脚本里涉及 HTTP 请求或需要模型能力,凭证散落在各个配置文件里会很难维护。我实测下来,用统一的 Key/API 通道来管理调用凭证,能省掉很多切换和排查的麻烦。
TaoToken 提供的就是这样一个统一入口。它的官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api 。你可以把它理解为一个凭证中转层:Claude Code、Cline、Codex 等工具都通过同一个 Base URL 和 Key 来发起请求,而不是每个工具单独配一套。
具体操作上,你需要先拿到 API Key。进入控制台的 API Keys 页面(https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ),创建一个新的 Key 并复制保存。这个 Key 后面会用在 Claude Code 的 settings 配置里,也会用在 Hook 脚本需要发起 HTTP 请求的场景中。
为什么要在 Hook 配置之前做这一步?因为 Claude Code 的 Hook 事件中,有一部分(比如 StopFailure)会在 API 错误时触发,如果你的凭证配置有问题,Hook 可能会频繁误触发,排查起来很痛苦。先把 Key 通道理顺,后面验证 Hook 时就能排除凭证因素的干扰。
配置凭证时需要注意三点:Base URL 要填https://taotoken.net/api,不要带多余的路径;Key 要完整复制,不要有空格;Model ID 要和你实际使用的模型名称一致。这三件套(Base URL + Key + Model ID)在 Claude Code 的 settings.json 里是绑定的,任何一项写错都会导致请求失败。
如果你用的是 Claude Code 的 Anthropic 兼容模式,还需要确认 API 版本头是否正确。这些细节在接入文档(https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite )里有说明,配置前建议先过一遍。凭证准备好之后,就可以进入 Hook 的实际配置环节了。
3. 可复制的 settings 配置片段与逐事件落地
Claude Code 的 Hook 配置写在 JSON 文件里,通常放在项目的.claude/settings.json或用户级的~/.claude/settings.json。配置结构是三层嵌套:第一层选择要响应的 Hook 事件,第二层添加 matcher 组来过滤触发条件,第三层定义一个或多个 hook 处理器。下面是一个完整的可复制片段,覆盖了 PreToolUse、PostToolUse 和 Stop 三个常用事件。
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "if": "Bash(rm *)", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh" } ] } ], "PostToolUse": [ { "matcher": "Edit", "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint-check.sh" } ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-summary.sh" } ] } ] } }这段配置里,matcher用来缩小触发范围。比如PreToolUse的 matcher 设为Bash,表示只在 Bash 工具调用时触发;if条件进一步缩小到以rm开头的命令。这样block-rm.sh只会在两个过滤器都匹配时才执行。PostToolUse的 matcher 设为Edit,表示只在文件编辑成功后触发 lint 检查。Stop事件没有 matcher,因为它在每轮响应完成时都会触发。
接下来是 Hook 脚本本身。以block-rm.sh为例,它从标准输入读取 JSON,提取命令内容,如果包含rm -rf就返回拒绝决策:
#!/bin/bash # .claude/hooks/block-rm.sh COMMAND=$(jq -r '.tool_input.command') if echo "$COMMAND" | grep -q 'rm -rf'; then jq -n '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "Destructive command blocked by hook" } }' else exit 0 fi这个脚本的关键点在于输出格式。Claude Code 期望 Hook 通过标准输出返回 JSON,其中permissionDecision设为deny时会阻止工具调用,设为allow或直接exit 0则放行。hookEventName必须和触发的事件名一致,否则决策不会被识别。
对于 HTTP 类型的 Hook,输入会作为 POST 请求体到达你的端点,你需要在响应中返回同样的 JSON 结构。这种模式适合把 Hook 逻辑放在远程服务上,比如统一的代码规范校验服务。配置时把type改为http,并指定url字段即可。
还有一个容易踩的坑:$CLAUDE_PROJECT_DIR环境变量在 Hook 执行时是可用的,它指向项目根目录。用绝对路径引用脚本能避免工作目录变化导致的找不到文件问题。如果你在CwdChanged事件后执行 Hook,这一点尤其重要。
配置写完后,需要重启 Claude Code 会话让 settings 生效。你可以在会话中输入/hooks命令查看当前加载的 Hook 列表,确认配置是否被正确解析。如果列表里没有你配置的事件,检查 JSON 格式是否有语法错误,或者文件路径是否放对了位置。
4. 验证请求与确认 Hook 按预期执行
配置写完不代表生效,必须逐事件验证。我试过几种验证方式,最直接的是在 Hook 脚本里加日志输出,然后观察 Claude Code 的实际行为。下面按事件类型分别说明验证方法。
对于PreToolUse,你可以让 Claude Code 执行一个包含rm -rf的命令,比如rm -rf /tmp/build。如果 Hook 生效,Claude Code 会显示命令被阻止,并给出你配置的permissionDecisionReason。同时,你可以在脚本里加一行echo "$(date) PreToolUse triggered" >> /tmp/hook.log,验证后查看日志文件确认脚本确实被执行了。
对于PostToolUse,让 Claude Code 编辑一个文件,然后观察 lint 脚本是否运行。你可以在lint-check.sh里写入:
#!/bin/bash INPUT=$(cat) FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path') echo "Lint check for: $FILE" >> /tmp/hook.log npx eslint "$FILE" >> /tmp/hook.log 2>&1 exit 0编辑文件后查看/tmp/hook.log,如果能看到文件路径和 eslint 输出,说明 Hook 在工具调用成功后正确触发了。
对于Stop事件,每次 Claude Code 完成响应时都会触发。你可以在脚本里记录时间戳和会话 ID,验证触发频率是否符合预期。注意Stop和StopFailure的区别:前者是正常完成,后者是 API 错误导致的中断。如果你发现StopFailure频繁触发,优先检查第 2 节的凭证配置。
HTTP 类型的 Hook 验证稍微麻烦一点。你需要一个能接收 POST 请求的端点,可以用nc或简单的 Python HTTP 服务临时测试:
python3 -c " from http.server import BaseHTTPRequestHandler, HTTPServer class H(BaseHTTPRequestHandler): def do_POST(self): length = int(self.headers['Content-Length']) body = self.rfile.read(length) print(body.decode()) self.send_response(200) self.end_headers() self.wfile.write(b'{}') HTTPServer(('localhost', 8080), H).serve_forever() "把 Hook 配置里的url指向http://localhost:8080,触发事件后观察终端输出,确认请求体和事件类型匹配。
验证过程中有一个关键点:Hook 的输入 JSON 结构在不同事件下是不一样的。PreToolUse的输入包含tool_input.command,PostToolUse包含tool_input.file_path,Stop则包含会话级别的元数据。你可以在脚本里先cat出完整输入,确认字段名后再写解析逻辑。我踩过的坑就是直接照搬示例里的字段名,结果因为事件类型不同导致jq解析为空。
另外,Hook 脚本的执行超时时间有限,默认是 60 秒。如果你的 lint 或校验逻辑耗时较长,建议改成异步执行,或者在脚本里加超时控制,避免阻塞主流程。
5. 本篇常见错误排查与真实报错对照
Hook 配置过程中会遇到几类典型报错,下面按现象、原因、解决方式逐一对照。
401 Unauthorized:这个报错通常出现在 Hook 脚本发起 HTTP 请求时,或者 Claude Code 本身调用模型 API 时。如果你在 Hook 里用了 TaoToken 的 API,检查 Key 是否完整、Base URL 是否写成https://taotoken.net/api(不要多加/v1或尾部斜杠)。如果 Claude Code 主流程报 401,检查 settings.json 里的凭证三件套是否齐全:Base URL、Key、Model ID。缺任何一项都会导致认证失败。
local proxy failed:这个报错说明 Claude Code 尝试通过本地代理发起请求但失败了。常见原因是环境变量里设置了HTTP_PROXY或HTTPS_PROXY,但代理服务没有运行。解决方式是检查环境变量,或者在 settings.json 里显式配置不走代理。注意不要使用任何非法的网络访问方式,统一走正规 API 通道即可。
reading choices 相关报错:这类报错通常出现在 Hook 返回的 JSON 格式不符合预期时。Claude Code 期望hookSpecificOutput里包含hookEventName和permissionDecision字段,如果你返回的 JSON 缺少这些字段,或者字段名拼写错误,就会解析失败。用jq -n构造 JSON 能避免手动拼接导致的格式问题。
OAuth 相关报错:如果你用的是需要 OAuth 认证的模型服务,Hook 触发时可能会因为 token 过期而报错。解决方式是刷新 token,或者改用 API Key 认证方式。TaoToken 的 API Key 认证不涉及 OAuth 流程,配置更简单。
Hook 不触发:配置写了但脚本没执行,先检查三个地方。第一,settings.json 的 JSON 语法是否正确,可以用jq . settings.json验证。第二,matcher 是否匹配到了实际的事件,比如PreToolUse的 matcher 设为Bash,但实际调用的是Edit工具,就不会触发。第三,脚本是否有执行权限,用chmod +x加上。
Hook 触发但决策不生效:脚本执行了,但deny没有阻止工具调用。检查输出的 JSON 里hookEventName是否和事件名完全一致,大小写敏感。另外,permissionDecision的值必须是allow、deny或ask之一,其他值会被忽略。
CC Switch / Cline MCP / Codex auth.json 配置问题:如果你同时使用多个工具,确保每个工具的配置里都写全三件套。CC Switch 的配置在~/.cc-switch/config.json,Cline MCP 在.cline/mcp.json,Codex 在~/.codex/auth.json。Base URL 统一填https://taotoken.net/api,Key 用同一个,Model ID 按工具要求填写。任何一处缺失都会导致该工具调用失败。
排查时建议打开 Claude Code 的详细日志模式,在 settings.json 里加上"logLevel": "debug",这样能看到 Hook 的输入输出和决策过程,定位问题会快很多。
6. 长期编码场景下的凭证与 Hook 管理建议
如果你打算长期在编码和 Agent 场景中使用 Claude Code Hook,凭证管理会变成一个持续性的问题。我的做法是把所有工具的 Base URL 和 Key 统一指向 TaoToken,这样切换工具时不需要重新配置凭证,Hook 脚本里的 HTTP 请求也能复用同一套认证信息。
对于需要长期运行的编码任务,Coding Plan 提供了更稳定的调用通道(https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ),适合把 Hook 校验、代码生成、自动化测试串成完整流水线。你可以把 Hook 脚本里的模型调用指向这个通道,减少因额度波动导致的中断。
验证模型行为是否符合预期时,模型对话页面(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite )可以快速测试提示词和响应格式,确认后再写入 Hook 脚本,避免反复触发事件来调试。
Hook 脚本本身建议纳入版本控制,放在项目的.claude/hooks/目录下,和 settings.json 一起提交。这样团队成员拉取代码后,Hook 配置自动生效,不需要每个人手动配置。脚本里的敏感信息(比如 API Key)不要硬编码,通过环境变量读取,环境变量在 settings.json 的env字段里配置。
最后一点经验:Hook 的触发频率比你想象的高,尤其是PostToolUse和Stop。脚本里避免做重操作,日志文件定期清理,否则磁盘很快会被写满。如果某个 Hook 逻辑复杂,考虑改成异步执行,或者只在特定 matcher 下触发,减少不必要的执行次数。