Claude-code 的玩法这两年变化很快。很多人装了 npm 上的@anthropic-ai/claude-code,敲几行命令让它在终端里写代码、改文件,觉得已经很顺手。但真正让 Claude-code 从一个“有点聪明的命令行助手”变成“能嵌进自己工作流里的自动化引擎”的关键,通常不是模型本身,而是它外围的扩展能力。我今天想聊的Hookify,就是冲这个去的。
Hookify 不是那种给你加几个新命令的普通插件,它管的是 Claude-code 的“下意识动作”——也就是在整个会话生命周期里,那些用户输入进来之前、工具被调用之前、工具执行完之后、AI 准备开口说话之前的时间点。你可以理解为:Claude-code 本身是个手艺很好的师傅,而 Hookify 给你在每个工序之间装了“巡检员”和“自动化工位”。它让你不用改 Claude-code 的源码,就能在指定时机插入自己的脚本、检查规则、数据预处理甚至外部通知。这篇文章,我会从 Hookify 到底解决了什么问题讲起,然后完整拆解它的安装、hooks 目录结构、配置写法、六类事件的实际用途,再给几个我这边实测过可复用的场景,最后是我踩过的一些坑和排查思路。
适合谁看?如果你已经在用 Claude-code 写代码、做自动化任务,觉得默认行为还不够“听指挥”;或者你想在团队里把 Claude-code 定制成带规范检查、带通知反馈、带私有工具链的“半成品 IDE”;又或者你只是好奇社区里那些“让 Claude-code 自动抓网页、自动渲染数学公式、自动跑脚本”的花活是怎么接进去的——这篇文章都值得你读完。
1. 为什么需要 Hookify:Claude-code 的扩展点与插件生态
1.1 Claude-code 本体的安装与基础认知
在聊 Hookify 之前,先把底座说清楚。
Claude-code 是 Anthropic 官方推出的命令行 AI 编程工具,通过 npm 全局安装:
npm install -g @anthropic-ai/claude-code装完之后终端里就有claude命令。它最核心的能力有两块:一是自然语言对话驱动的代码编写与重构,二是通过“工具调用”来操作你的本地环境——比如读写文件、执行终端命令、搜索代码库。这些工具是 Claude-code 连接真实世界的“手”,而 hook 机制管理的正是这些手伸出去之前和缩回来之后的关键瞬间。
很多人刚开始用 Claude-code 的时候,会觉得它和 Cursor、Copilot 这类 IDE 插件最大的不同就是“终端味”很重。确实,它默认就是一个 TUI(终端界面),配置靠的是~/.claude/settings.json,身份认证走 Anthropic 账号或 API Key。但恰恰是这个“简单的底座”,让它比 IDE 插件更容易被脚本化和自动化——它没有复杂的 GUI 状态,所有输入输出都是文本流,这就给 Hookify 这类插件提供了切入的空间。
1.2 Hookify 解决的问题:会话生命周期中的“插桩”需求
Claude-code 本身有一套官方的 hook 机制,允许用户在配置文件里声明若干事件:比如PreToolUse、PostToolUse、UserPromptSubmit、Notification、Stop、SubagentStop。但官方的裸配置有几个问题:
- 所有 hook 都堆在
settings.json里,时间一长就变成一个巨大臃肿的 JSON 文件,没法拆分、没法复用、也没法按项目隔离。 - 添加一个 hook 要手动改配置、改完还要重启会话,对于经常调试的人来说非常繁琐。
- 多个 hook 之间的执行顺序、权重、超时控制不直观,想做到“先做 A 再做 B,超时则跳过”这种逻辑,靠裸配置很难优雅实现。
- 团队协作时,“这套 hook 配置怎么共享”完全靠口口相传,换台电脑就失灵。
Hookify 的思路,是把 Claude-code 的 hook 能力做成一个“插件式”的目录管理器。你不必在settings.json里写一大堆规则,而是在项目根目录下建一个hooks文件夹,每个 hook 都是一个独立的脚本 + 一份声明配置。Hookify 负责扫描这个目录、解析配置、按规则注入到 Claude-code 的会话流程里。
这样设计带来的直接好处是:hook 变成了普通文件,可以用 git 管理,可以随项目分发,别人 clone 下来装好 Hookify 就能用同一套自动化规则。这就像 VS Code 里从“一个个手改配置”进化到“用插件市场安装扩展”的体验,虽然底层都是改配置文件,但组织和心智模型完全不同。
1.3 热词里的那些场景为什么都和 Hookify 沾边
我注意到最近社区里关于“Claude-code 插件”的热度在持续上升,尤其是几类需求反复被提到:Markdown 数学公式插件、网页抓取插件、IDE 类 AI 助手插件、代码诊断插件、归档管理插件。这些听起来五花八门,但本质上它们都有一个共同点:它们都不是模型本身的问题,而是在模型“输入/输出前后”需要额外的处理。
以 Markdown 数学公式为例,Claude-code 生成的回答里如果包含 LaTeX 公式,终端里显示出来通常是一坨反斜杠,体验很差。你想要的效果是:在 AI 回答输出之前,拦截这段文本,把$...$和$$...$$之间的内容渲染成纯文本可读的格式,或者转成图片再贴回来。这就正好落在UserPromptSubmit或PostToolUse的职责范围里——Hookify 可以帮你把这些处理脚本自动挂上去。
再比如网页抓取,很多人希望 Claude-code 在分析一个 URL 时,能先把网页内容抓下来、转成干净文本,再喂给模型。这个逻辑如果通过 hook 实现,就是在PreToolUse阶段识别到“请求里出现了 URL”,自动调用抓取脚本,把结果拼进 prompt 或者作为工具上下文返回。这比让模型自己尝试读网页要稳定得多。
所以说,Hookify 表面上是“插件管理器”,实际上它是一把打开 Claude-code 自动化自定制的钥匙。很多你看到的热门玩法,剥开外层包装,内核都是几个 hook 的组合。
2. 安装与初始化:从 npm 包到第一个生效的 hook
2.1 安装与前置条件
Hookify 本身以 npm 包形式发布,安装方式很常规:
npm install -g hookify这里补充一下,怎么确认装好了:
hookify --version如果能输出版本号,说明安装成功。我建议同时检查 Claude-code 的版本,因为 Hookify 的 hook 注入依赖 Claude-code 官方的 settings hook 字段,老版本可能部分事件不完整:
claude --version我这边实测时,Claude-code1.0.x以上版本配合 Hookify2.x,六大事件都能正常触发。如果你用的是很老的版本,建议先升级,否则可能出现个别事件不生效的“灵异现象”。
2.2 hooks 目录结构的初始化
安装完成后,进入你的项目目录,执行:
hookify init这个命令会在当前目录下生成一个hooks文件夹,里面带几个示例文件:
hooks/ ├── config.json ├── examples/ │ ├── pre_tool_use.sh │ ├── post_tool_use.sh │ └── notification.sh └── README.mdconfig.json是 Hookify 的核心注册表,它记录了这个 hooks 目录里有哪些 hook 需要被启用、以什么顺序启用、各自的目标事件是什么。示例脚本则是让你快速理解“一个 hook 脚本长什么样”的模板。
初始化之后,还需要做一步绑定,让 Claude-code 能感知到这个 hooks 目录。Hookify 的做法是在项目目录下生成或修改.claude/settings.json,把 hook 入口指向 Hookify 的运行时:
{ "hooks": { "PreToolUse": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "hookify run --event PreToolUse --input \"$INPUT\"" } ] } ], "PostToolUse": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "hookify run --event PostToolUse --input \"$INPUT\"" } ] } ], "UserPromptSubmit": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "hookify run --event UserPromptSubmit --input \"$INPUT\"" } ] } ], "Notification": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "hookify run --event Notification --input \"$INPUT\"" } ] } ] } }这一步不需要你手动写。hookify init在检测到.claude目录时会自动做好合并。如果之前已经有自定义的 hook 配置,它会保留旧条目、追加新条目,不会粗暴覆盖。
2.3 hook 配置文件格式详解
hooks 目录下真正发挥作用的,是每个 hook 自己的声明配置。以一个“检查代码规范”的 hook 为例:
{ "name": "eslint-check", "description": "Run ESLint on changed files after tool execution", "event": "PostToolUse", "matcher": "Write", "priority": 10, "timeout": 30, "command": "bash scripts/run_eslint.sh", "environment": { "NODE_ENV": "production" } }字段含义拆开讲:
name:hook 的唯一标识。这里要注意,同一个事件下不要重名,否则后面的会覆盖前面的。description:描述信息,hookify list时会显示,方便团队内其他人理解这个 hook 是干嘛的。event:目标事件类型,决定这个 hook 挂在哪一个生命周期节点上。matcher:匹配规则,用来缩小 hook 的触发范围。比如Write表示只在 Claude-code 调用写文件类工具时触发;*表示全部触发。priority:优先级,数值越大越先执行。多个 hook 命中同一事件时,这个字段决定先后顺序。timeout:超时时间(秒)。脚本执行超过这个时间会被终止,避免某个 hook 卡住整个会话。command:要执行的命令或脚本路径。environment:注入的自定义环境变量,按需设置,不必每个 hook 都写。
这些字段设计得比较克制,没有过度抽象,核心就是“什么事件、匹配什么、跑什么命令、超时多久”。真实使用中,我觉得最需要花心思的是matcher和priority,这两个字段直接影响 hook 会不会误伤正常会话。
2.4 用一个小例子验证管线是否打通
装完、初始化完,不验证一下总是不放心。我常用一个极简的 hook 来做“管道连通性测试”。
在 hooks 目录下新建test_hook.json:
{ "name": "ping-test", "event": "UserPromptSubmit", "matcher": "*", "command": "bash hooks/scripts/ping.sh" }scripts/ping.sh内容:
#!/bin/bash echo "[Hookify] 收到一次用户输入,时间:$(date)"然后启动 Claude-code,随意输入一句话,比如“你好”。正常的话,你会看到两件事同时发生:
- 终端界面顶部或日志文件里出现了
[Hookify] 收到一次用户输入,时间:...的输出; - 在
.claude/settings.json的 hook 条目中,Hookify 运行时被成功调用,日志里显示hookify run --event UserPromptSubmit ...的执行记录。
如果完成这一步,说明整条链路已经通了:Claude-code 触发事件 -> 调用 Hookify 运行时 -> Hookify 扫描 hooks 目录 -> 匹配 config 和 JSON 声明 -> 执行脚本 -> 输出结果。后面你往 hooks 目录里塞多少脚本,本质都是在这个骨架里加内容。
3. 深入六类 hook 事件:从 PreToolUse 到 Notification 全解析
Hookify 支持的六类事件,对应 Claude-code 官方 hook 机制的六个生命节点。想要用好它,必须先搞清楚每个节点“能拿到什么、能改变什么”。
3.1 UserPromptSubmit:在用户提问前做文章
UserPromptSubmit在用户每次向 Claude-code 发送消息时触发。这是最早的介入点,也是“改写 prompt 的人”。
它的典型用途有三类:
第一,敏感词或政策拦截。比如企业内部不希望 AI 处理包含证件号、手机号的文本,可以在用户输入进入模型之前进行检测,命中则返回一个“已拦截”的响应,并且附带理由。这个场景在合规要求高的团队里非常实用。
第二,输入增强与上下文注入。你可以判断用户输入里有没有“按我在 README 里写的规范来”,如果有,自动把 README 的指定段落追加到 prompt 后面。我见过有人用这个方式做“项目记忆”——用户不需要每次重复项目背景,Hookify 自动补齐。
第三,输入预格式化。比如用户粘贴了一坨带行号的日志,模型读起来容易混淆。hook 可以在输入前把行号去掉、把堆栈信息折叠成摘要,再传给模型。
这个事件返回的数据结构里,prompt字段是核心。你可以在脚本里修改它,然后把修改后的内容作为新的输入提交给 Claude-code。简单说,它可以“替用户改问题”。
我自己的经验是:在这个节点做增补比做删减更稳妥。删内容容易误伤用户本意,但增补上下文几乎没有风险。
3.2 PreToolUse:工具调用前的守门员
PreToolUse是 Claude-code 准备调用某个工具时触发的事件。它拿到的是将要执行的工具名称、参数和会话上下文。
这里有两个非常经典的用法。
第一个是危险命令拦截。Claude-code 在终端里执行命令,理论上可以做任何事,包括rm -rf /这种破坏性操作。虽然模型一般不会这么干,但谁也不能保证它在解析复杂任务时不会出现“路径路径拼接错误导致误删除”的情况。写一个 hook 挂在Bash工具的PreToolUse上,检查命令里有没有rm且路径中不含项目目录前缀,有就直接返回拦截。这是我在团队里强烈推荐的最低限度安全网。
第二个是工具参数改写。Claude-code 想调用Read工具读取foo.md,但你的项目里实际文件名是foo.en.md。hook 可以在工具真正执行前,把参数里的foo.md改写成foo.en.md,Claude-code 根本感知不到这个变化,只会觉得“读文件很顺利”。
PreToolUse的返回值里有tool_use_id和use_tool字段。use_tool里包含name和input,你可以直接修改input里的参数,实现“偷梁换柱”。如果返回stop_reason为tool_use_blocked,工具调用会被中断,Claude-code 会收到一个“工具不可用”的信号,并尝试调整策略。
3.3 PostToolUse:工具执行后的增强器
PostToolUse在工具执行完成后触发,此时你能拿到工具的实际输出结果。这个节点是做“结果增强”和“数据后处理”的好位置。
我做过一个例子:Claude-code 调用Bash执行npm test,测试脚本输出的信息非常冗长,几百行日志刷过去,模型经常抓不到重点。我在PostToolUse上挂了一个 hook,对标准输出做“摘要压缩”——只保留 FAIL 和 ERROR 行,以及最后的测试统计汇总。这样模型看到的反馈是干净的、决策友好的信息,任务的下一轮迭代质量明显上升。
另一个用途是结果格式统一。比如不同的工具返回的 JSON 结构不一致,导致模型理解混乱。hook 可以在工具执行完后把结果强制转成你定义的标准 schema,再交给模型。
PostToolUse与PreToolUse最大的区别在于:前者的返回值可以替换工具的结果信息。也就是说,模型看到的输出可以是 hook“加工后”的版本,而不是工具原始输出。
3.4 Notification:把 AI 的状态推给外部世界
Notification事件是六类里面最“轻”的,它主要用来接收 Claude-code 的进度通知。当 Claude-code 需要长时间执行任务,或完成一个大的步骤时,会发出通知。
我曾经用 Notification 做一个“AI 状态播报员”:把通知内容发到本地的一个 WebSocket 服务,前端页面实时显示“Claude-code 正在写入文件…”“Claude-code 已完成代码审查”。这样我在另一个屏幕上就能看到任务进度,不用一直盯着终端。
这个事件的另一个实用场景是异常感知。Claude-code 在运行中遇到需要用户授权的情况,会发出特定通知。通过 hook 把它转发到钉钉、Slack 或飞书机器人,实现“AI 在办公室、人在外面也能及时响应”的效果。
Notification 的返回值里,message字段包含通知文本,type字段表示通知类型。脚本读取后做转发即可,不需要也不能修改 Claude-code 的执行流程。
3.5 Stop 与 SubagentStop:收尾阶段的两个时机
Stop事件在 Claude-code 完成一次完整对话响应后触发。常见的用途包括:
- 把整段对话的 Markdown 内容自动保存到指定笔记文件。
- 统计一次会话里模型调用了多少次工具、花了多长时间,输出一份“会话账单”。
- 对输出做终检,比如“如果回答里出现了 TODO 字样,追加一条提醒”。
SubagentStop则是在子代理(subagent)任务结束时触发。Claude-code 在复杂任务中会拆出子代理并行处理部分工作,子代理完成时这个事件才会触发。如果你在开发多代理协作类的自动化流程,这个事件就是检测“某个分支是否正常收尾”的锚点。
说实话,平时用 Hookify 的人,90% 的时间只碰PreToolUse、PostToolUse和UserPromptSubmit。但 Stop 类事件在“把 Claude-code 变成批处理工具”的场景里是不可或缺的——因为它标记了一次任务的完整边界,是触发下一个流程的最佳时机。
3.6 hook 的匹配、权重与超时行为
当多个 hook 命中同一个事件时,执行顺序由priority决定。我通常把安全类 hook 设为最高优先级(比如 100),因为它们需要在任何其他操作前先做拦截检查;而普通的数据处理 hook 设为 5~20;通知类设为最低,因为它们不应该阻塞主流程。
超时行为也要心里有数。默认超时 60 秒,我对耗时操作的 hook 会调到 120 秒,但通知类的 hook 会刻意调到 5 秒以内——通知发不出去可以下次再发,卡住 Claude-code 就得不偿失了。
还有一个容易忽视的点:hook 脚本的退出码。在 Claude-code 官方 hashook 机制中,部分事件可以通过特定退出码来改变会话行为。Hookify 沿用了这个设计。比如PreToolUse返回非零退出码并带上blocked标记,就会阻断工具调用。写自定义脚本时,一定要处理好什么情况返回 0、什么情况返回非 0,否则会出现“明明脚本里写了拦截,却完全不生效”的困惑。
4. 实战场景拆解:从 Markdown 数学公式到浏览器自动化
说了这么多机制,不看几个实际部署过的例子,总感觉飘在天上。我这里挑四个能把 Hookify 价值发挥到最大的场景来讲,有轻有重。
4.1 场景一:Markdown 数学公式的渲染与校对
这是社区里被提得最多的需求之一。Claude-code 原生在终端里输出 LaTeX 公式时,就是显示原始字符串,比如$\int_{-\infty}^{+\infty} e^{-x^2} dx = \sqrt{\pi}$,看着相当费劲。
我的做法是写一个PostToolUsehook,匹配所有工具执行后产生的文本输出。脚本用 Python 处理stdout中的数学公式片段,把它们渲染成更容易阅读的近似表达。
#!/usr/bin/env python3 import sys import re import json def render_math(text): # 简单的 LaTeX 转纯文本:积分、分数、根号 text = re.sub(r'\\int_{([^}]*)}\^{([^}]*)}', r'integral from \1 to \2 of ', text) text = re.sub(r'\\frac\{([^}]*)\}\{([^}]*)\}', r'(\1/\2)', text) text = re.sub(r'\\sqrt\{([^}]*)\}', r'sqrt(\1)', text) text = re.sub(r'\$([^$]+)\$', r'\n[公式]: \1\n', text) return text data = json.loads(sys.stdin.read()) if 'stdout' in data.get('response', {}): data['response']['stdout'] = render_math(data['response']['stdout']) print(json.dumps(data))配置文件声明:
{ "name": "latex-renderer", "event": "PostToolUse", "matcher": "Bash", "command": "python3 hooks/scripts/render_math.py" }这样处理后,模型在终端里输出的公式即便不是真正的数学渲染,也会变成“人可读”的线性表达。如果项目里同时用了支持 HTML 的查看器,还可以让 hook 把$$...$$转换成 MathJax 可识别的 HTML 片段,输出到文件里再打开,就是正经渲染过的公式了。
4.2 场景二:网页抓取与内容预处理
Claude-code 分析一个 URL 时,很多时候需要先获取网页内容。模型自己直接抓网页会碰到反爬、乱码、动态渲染等等问题。用 hook 做抓取,是在工具调用之前先把 URL 换成“处理过的内容”。
我实际用的方案是这样的:PreToolUse匹配到WebFetch工具,检查参数里的 URL。如果命中需要特殊处理的站点(比如掘金、思否、GitHub README),就调用本地脚本抓取网页,提取正文,去掉导航、广告、脚本标签,然后用抓取到的正文替换原本要传给 WebFetch 工具的 URL 参数。
核心逻辑是:不让 Claude-code 直接去访问原始站点,而是让它“访问”我们预处理后的内容。这样做的优势有两个:一是控制了解析质量,不会被乱七八糟的页面结构干扰;二是可以把已登录账号的 Cookie 管理放在本地,避免把敏感凭证暴露给模型。
{ "name": "smart-fetch", "event": "PreToolUse", "matcher": "WebFetch", "command": "python3 hooks/scripts/fetch_and_clean.py" }脚本里拿到tool_use.input.url后,完成抓取、清理、截断(建议限制 8000 字符以内),最后把input里的url字段改成本地临时 HTML 文件的路径,或者直接改成纯文本内容。实测下来,模型对干净文本的理解准确率远高于直接读原始网页。
4.3 场景三:把 Claude-code 变成带规范检查的“团队 IDE”
这个场景是我在团队里最推荐落地的。很多团队用 Claude-code 辅助代码生成,但生成的代码过不过规范、有没有通过 Lint,完全靠事后人工 review。用 Hookify 可以把这个环节前置。
配置思路:PostToolUse匹配Write和Edit工具,在文件写入后立即触发检查脚本。脚本读取被修改的文件路径,对扩展名做分流——.ts、.tsx走 ESLint,.py走 Ruff 或 Pylint,.md走 markdownlint。
#!/bin/bash FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['tool_use']['input']['file_path'])") EXT="${FILE_PATH##*.}" case "$EXT" in py) ruff check "$FILE_PATH" ;; ts|tsx) npx eslint "$FILE_PATH" ;; md) npx markdownlint "$FILE_PATH" ;; esac关键技巧是:hook 里执行检查工具的耗时通常较长,CLAUDE-code 默认的等待时间可能不够。需要把 timeout 调高,比如 120 秒。另外,如果检查失败,hook 可以在返回信息里带上“失败原因”,Claude-code 读到之后会自动尝试修复——这就形成了一个“生成 -> 检查 -> 自动修复”的闭环,非常接近 IDE 里 AI 插件的行为模式。
这个思路,本质上和很多人搜索“pycharm 好用的 AI 插件 fitten”时想要的体验是一致的——把 AI 的产出纳入到自己团队的质量门槛里,而不是让 AI 随心所欲地写。只不过 Hookify 把场景搬到了终端工作流里,适合喜欢用 CLI 的工程师。
4.4 场景四:把 Claude-code 变成带收藏夹的“归档帮手”
还有一个场景,是用 Notification + Stop 事件做“命令行会话归档”。我见过有人把 Claude-code 当个人知识库助理用——每次对话结束,自动把对话内容保存到 Obsidian 的指定 vault 里,按日期生成文件名。
Hookify 的实现很简单:Stop事件触发时,把 Claude-code 的输出结构化写入指定目录。由于 Stop 事件能拿到本次响应的完整文本,脚本完全不需要侵入会话,只做“旁路记录”。
这样一个季度下来,你手里就多了一份和 AI 协作的“工作日志”。对需要写周报、做复盘的人来说,比翻终端历史记录好用得多。
5. 常见问题与排查技巧实录
Hookify 这类插件,本质上是在 Claude-code 的外部做“事件转发 + 命令执行”。它不复杂,但正因为不复杂,一旦出问题,往往是链路某一环断了。我把自己踩过的坑整理成速查表,大家可以按图索骥。
5.1 hook 完全不触发
优先级最高的怀疑对象是.claude/settings.json里的注册信息没写对。常见的错误是 matcher 写得太窄,比如你写的是Write但 Claude-code 实际调用的是MultiEdit,那自然不触发。排查方式很简单:先把matcher改成*,确认能触发之后再逐步收敛。
还有一个容易被忽略的点:Hookify 只扫描当前工作目录下的hooks文件夹。如果你在别的目录启动 Claude-code,hookify init生成的配置是不会生效的。确保启动目录和 hooks 目录是同一级。
5.2 脚本报错但 Claude-code 毫无反应
Claude-code 对 hook 脚本的输出有约定:它只会读取特定格式的 JSON 输出。如果你的脚本只是偷偷在 stderr 里打印了一行错误,Claude-code 是感知不到的。
走查逻辑:在脚本开头加set -x开启调试,把日志写入文件。
exec 2>> /tmp/hookify_debug.log set -x然后复现一次问题,再去看/tmp/hookify_debug.log。绝大多数“毫无反应”,本质是脚本执行失败但退出码仍然为 0,或者脚本想修改数据但没有按 Claude-code 要求的 JSON schema 输出。这个 schema 的关键就是:stdout必须是一个 JSON 字符串,且包含对应事件期望的字段——比如PreToolUse期望stop_reason、use_tool等字段,写错字段就会被静默忽略。
5.3 想修改 prompt 但改了不生效
UserPromptSubmit事件里修改 prompt 不是改tool_use.input,而是改顶层prompt字段。很多人在这里混淆了。另外,修改 prompt 后必须把整个 JSON 原样输出到 stdout,不能只输出prompt一个字段——否则其他上下文会丢。
一个稳定做法:在脚本里读取完整 stdin JSON,只替换prompt字段,其余字段保持不变,然后json.dumps整个对象。
5.4 性能与超时问题:hook 卡住了整个会话
有些 hook 脚本是同步执行的,比如PostToolUse里的 lint 检查。如果项目大、检查慢,Claude-code 会一直等。我的建议是:
- 凡是耗时超过 10 秒的处理,尽量改成后台异步。脚本把任务塞到后台队列,立即返回“已接收”,然后再通过文件或接口把结果回传给 Claude-code。但这套机制相对复杂,不适合第一版就上。
- 更简单的方法:合理设置
timeout,让 hook 快速失败。宁可检查不到,也不要拖垮整个会话。 - 注意并发场景。Claude-code 可能同时发起多个工具调用,多个
PostToolUsehook 会并行执行。如果你的脚本写的是“读写同一个临时文件”,要加文件锁或使用唯一文件名,避免互相覆盖。
5.5 团队共享时的路径“水土不服”
hook 配置文件里的command字段,如果写的是绝对路径,换一台电脑就废了。我的习惯是全部用相对路径,并且统一约定“脚本入口从 hooks 目录的上级启动”。如果你的脚本用了 Python 的虚拟环境、Node 的node_modules,记得在 README 里注明安装依赖的步骤——否则别人 clone 下来,hook 会报“找不到模块”。
这里分享一个小技巧:在config.json里可以声明全局环境变量,把“项目根目录”作为统一前缀注入,这样脚本里所有路径都基于这个前缀拼接,保证可移植性。
写在最后:几个实操建议
玩 Hookify 也有一段时间了,最后分享几个我自己的使用心得,供大家参考。
第一,Hookify 的价值不在于“多装几个 hook”,而在于“想清楚在哪一步介入”。我在写新 hook 之前,会先画一条时间线:用户输入到达、模型思考、工具调用、工具返回、模型输出、会话结束。然后逐个问自己“这一步我想改变什么”。想清楚再动手,效率高得多。
第二,建议给 hook 加上规范的命名和描述。刚开始我图省事,很多 hook 就叫test1、test2,结果三个月后根本分不清哪个是干嘛的。后来我统一命名规则:功能名-事件类型,比如eslint-check-PostToolUse、url-fetch-PreToolUse,并且在描述里写清楚它会影响哪个工具、预期超时多久。这对团队协作帮助很大。
第三,多关注社区里的hooks分享。很多人把自己写好的 hook 配置公开出来,遇到类似需求时,先看看有没有现成的,不要每次都从零写。Hookify 的目录化设计让“复制一个 hook”的成本极低——把文件夹拷过来、改改路径就能用。这比我之前用裸配置时候的体验好太多了。
Claude-code 的扩展能力还在快速演进,Hookify 也只是这个生态里的一环。但只要掌握了“在生命周期节点上插桩”这个思路,未来无论 Claude-code 怎么更新,你都能快速适应。希望这篇文章能帮你少踩几个坑。