用 Claude Code 跑复杂任务的人应该都有这个体验:
任务丢进去,然后你去干别的。
五分钟后回来——不知道跑完没。
十分钟后——不确定是正常结束还是报错了。
再回来——翻了半天终端,才搞明白上次到底做了什么。
整个等待过程,你的注意力是碎的。
我一直在想怎么解决这个问题。
有人推荐过 tmux——确实是个专门做这个的工具,通知做得挺漂亮。
但它只支持 Mac。
我两台电脑,一台 Mac 一台 Windows,跨平台工作是常态。装一个只能在一台机器上用的通知工具,换台电脑就失效,比没有更烦。
飞书有通知 bot,但我不想为了这个专门装一个 App。企业微信太重。Slack 在国内根本用不了。
我用的是钉钉——工作原因,已经开着了,就顺手用它。两台电脑都有,通知都能收到,这才叫解决问题。
解法不复杂,但我一开始没往这个方向想
Claude Code 有个东西叫 Hooks。
它允许你在对话生命周期的特定节点,执行自定义脚本。其中有一个 Stop Hook,会在每次对话结束时自动触发。
这就是注入通知逻辑的位置。
思路很清晰:对话结束 → 触发脚本 → 脚本调钉钉 Webhook → 我手机收到消息。
整套实现我直接让 Claude Code 自己写的。描述需求,它把脚本和配置一起交付。
核心步骤就四个:
- 在
~/.claude/settings.json里注册 Stop Hook,指向通知脚本 - 脚本从 stdin 读取 Hook JSON(里面有 transcript_path、session_id 这些)
- 解析 transcript JSONL,把 assistant 最后一条消息提取出来当摘要
- 调钉钉 Webhook,发一条带时间、项目名、摘要的 Markdown 消息
有一个细节必须处理: 脚本里要检查 stop_hook_active 字段。如果这个字段是 true,说明当前已经在 Stop Hook 执行中了,要直接退出——不然会无限循环。
另外脚本必须以 sys.exit(0) 结束,通知失败了也不能阻塞 Claude Code 的正常退出流程。
跑起来之后,我重构了一次
第一版是个单文件脚本,能跑,但有个问题——
万一哪天我想同时通知钉钉和企业微信,就得在同一个文件里堆逻辑。越堆越乱。
于是做了一次架构重构,引入了「通知适配器」的设计模式。
目录结构长这样:
~/.claude/hooks/
├── notify.py ← 主入口,读 Hook 数据,调度所有 adapter
├── config.json ← 控制哪些 adapter 启用
├── core/
│ ├── transcript.py ← 解析 JSONL,提取摘要和对话轮数
│ └── message.py ← 定义平台无关的 MessagePayload
└── adapters/
├── base.py ← 抽象基类 BaseAdapter
├── dingtalk.py ← 钉钉实现(已跑通)
├── slack.py ← skeleton,待填
└── wecom.py ← skeleton,待填
设计原则三条:
BaseAdapter定义format()和send()两个抽象方法,所有渠道必须实现MessagePayload是平台无关的数据结构,各 adapter 自己负责格式转换config.json控制开关,加新渠道不改任何核心代码
async: true 让通知异步发送,不卡 Claude Code 退出。 这个很重要,不然每次对话结束都要等通知发出去才退,体验很差。
加一个新渠道,现在只需要三步
架构搭好之后,扩展新渠道的成本接近零。
以飞书为例:
- 新建
adapters/feishu.py,继承BaseAdapter,实现format()和send() - 在
config.json加一行:{"name": "feishu", "enabled": true, "webhook": "..."} - 完成。
notify.py、transcript.py、message.py一行不动
这就是适配器模式的价值——扩展点是隔离的,新增不影响已有逻辑。
还能做什么
现在这套系统解决了「知道任务结束了」的问题。
下一步想做的:
- 接入 Anthropic API,对长对话生成结构化摘要——现在只是截取最后一条消息,信息量不够
- 多维度过滤——只在任务耗时超过 5 分钟、或者涉及特定项目时才发通知,不然容易变成噪音
- 区分正常结束和异常中止——报错了要发不同级别的提醒,不能和正常完成混在一起
- 历史归档——每次对话摘要追加到本地日志,方便回溯
顺便说一个概念辨析
做这个东西的过程中,有人问我:「这是不是 Skill?」
不是。
Skill 是给 AI 读的知识文档,告诉模型「怎么完成某类任务」。它没有运行时,不执行代码,本质是提示词工程的产物。
这个通知系统是运行时代码,是 Claude Code Lifecycle Hook + Notification Adapter。Hook 是触发机制,Adapter 是执行层。
一句话区分清楚:
| 概念 | 是什么 |
|---|---|
| Skill | 给 AI 看的知识,没有运行时 |
| Plugin | 宿主程序提供扩展接口,你按协议注册 |
| Hook Script | 生命周期事件触发的脚本,事件驱动 |
| Tool | AI 可以主动调用的能力单元 |
我们做的是 Hook,不是 Skill,也不是 Plugin。
整件事的本质是:把被动等待,变成主动通知。
Claude Code 跑完任务,钉钉告诉我。我去做别的,注意力不再被无谓地占用。
就这么简单。
更多深度内容与完整文章,欢迎关注我的微信公众号:SamLai 效率研习社
主要分享:
AI 编程与开发效率
技术趋势与工程思考
实用工具与工作流