Claude Code 跑完任务,它自己发钉钉通知我——我TM怎么没早想到

0 阅读5分钟

用 Claude Code 跑复杂任务的人应该都有这个体验:

任务丢进去,然后你去干别的。

五分钟后回来——不知道跑完没。

十分钟后——不确定是正常结束还是报错了。

再回来——翻了半天终端,才搞明白上次到底做了什么。

整个等待过程,你的注意力是碎的。

我一直在想怎么解决这个问题。

有人推荐过 tmux——确实是个专门做这个的工具,通知做得挺漂亮。

但它只支持 Mac。

我两台电脑,一台 Mac 一台 Windows,跨平台工作是常态。装一个只能在一台机器上用的通知工具,换台电脑就失效,比没有更烦。

飞书有通知 bot,但我不想为了这个专门装一个 App。企业微信太重。Slack 在国内根本用不了。

我用的是钉钉——工作原因,已经开着了,就顺手用它。两台电脑都有,通知都能收到,这才叫解决问题。


解法不复杂,但我一开始没往这个方向想

Claude Code 有个东西叫 Hooks

它允许你在对话生命周期的特定节点,执行自定义脚本。其中有一个 Stop Hook,会在每次对话结束时自动触发。

这就是注入通知逻辑的位置。

思路很清晰:对话结束 → 触发脚本 → 脚本调钉钉 Webhook → 我手机收到消息。

整套实现我直接让 Claude Code 自己写的。描述需求,它把脚本和配置一起交付。

核心步骤就四个:

  1. ~/.claude/settings.json 里注册 Stop Hook,指向通知脚本
  2. 脚本从 stdin 读取 Hook JSON(里面有 transcript_path、session_id 这些)
  3. 解析 transcript JSONL,把 assistant 最后一条消息提取出来当摘要
  4. 调钉钉 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 退出。 这个很重要,不然每次对话结束都要等通知发出去才退,体验很差。


加一个新渠道,现在只需要三步

架构搭好之后,扩展新渠道的成本接近零。

以飞书为例:

  1. 新建 adapters/feishu.py,继承 BaseAdapter,实现 format()send()
  2. config.json 加一行:{"name": "feishu", "enabled": true, "webhook": "..."}
  3. 完成。notify.pytranscript.pymessage.py 一行不动

这就是适配器模式的价值——扩展点是隔离的,新增不影响已有逻辑。


还能做什么

现在这套系统解决了「知道任务结束了」的问题。

下一步想做的:

  • 接入 Anthropic API,对长对话生成结构化摘要——现在只是截取最后一条消息,信息量不够
  • 多维度过滤——只在任务耗时超过 5 分钟、或者涉及特定项目时才发通知,不然容易变成噪音
  • 区分正常结束和异常中止——报错了要发不同级别的提醒,不能和正常完成混在一起
  • 历史归档——每次对话摘要追加到本地日志,方便回溯

顺便说一个概念辨析

做这个东西的过程中,有人问我:「这是不是 Skill?」

不是。

Skill 是给 AI 读的知识文档,告诉模型「怎么完成某类任务」。它没有运行时,不执行代码,本质是提示词工程的产物。

这个通知系统是运行时代码,是 Claude Code Lifecycle Hook + Notification Adapter。Hook 是触发机制,Adapter 是执行层。

一句话区分清楚:

概念是什么
Skill给 AI 看的知识,没有运行时
Plugin宿主程序提供扩展接口,你按协议注册
Hook Script生命周期事件触发的脚本,事件驱动
ToolAI 可以主动调用的能力单元

我们做的是 Hook,不是 Skill,也不是 Plugin。


整件事的本质是:把被动等待,变成主动通知。

Claude Code 跑完任务,钉钉告诉我。我去做别的,注意力不再被无谓地占用。

就这么简单。


更多深度内容与完整文章,欢迎关注我的微信公众号:SamLai 效率研习社

主要分享:

AI 编程与开发效率

技术趋势与工程思考

实用工具与工作流