
1. 长任务跑起来之后我为什么总在切窗口Claude Code 这类 AI Agent 工具最舒服的用法是让它接一个稍微大一点的任务重构一个模块、补一整套单元测试、把某个接口从同步改成异步。这种活儿通常要跑几分钟到十几分钟中间还会穿插读文件、跑命令、改代码。问题就出在这段时间里——你根本不知道它到底跑完没有。我自己的习惯特别典型让 Claude Code 开始干活然后切到浏览器查个文档过两分钟忍不住切回来看看进度发现还在跑再切走再过两分钟又切回来。一个十分钟的任务我可能来回切了七八次。更难受的是权限确认Claude Code 执行到某一步需要你授权一条 shell 命令它会在那里静静等你点确认而你正在另一个窗口里以为它还在忙。等你十分钟后切回来发现它从第八分钟就卡住了前面全白等。这个问题的本质不是 Claude Code 慢而是任务完成状态没有主动推送到你面前。它是个本地 CLI 工具跑在终端里默认不会弹系统通知也不会给你发消息。你只能靠主动去看来感知状态而人一旦进入别的工作流就会忘记回来看。我一开始也偷懒想让 AI 自己解决。最常见的AI 方案是在~/.claude/CLAUDE.md里加一段提示词告诉模型任务完成后播放一个提示音比如## Task Completion Sound When you complete a task, play a sound: afplay /System/Library/Sounds/Glass.aiff实测下来这东西极不稳定。有时候响有时候不响完全看模型当时心情。原因也不难理解提示词是软约束模型对生成文本以外的操作类指令本来就不太可靠它可能觉得当前场景不需要播放声音就跳过了长对话里上下文还会被压缩这段指令的优先级会被降低甚至直接丢掉再加上什么算任务完成每次理解都不一样触发时机飘忽不定。所以正确的思路是确定性的动作交给确定性的机制。Claude Code 提供了 Hook 系统它是在特定事件发生时由工具本身去执行一段命令不经过模型判断触发是 100% 的。这篇就带你从零把 Hook 通知配起来再封装成一个可复用的 SKILL让任务跑完、需要授权时主动提醒你。适合谁看本地用 Claude Code 做开发的、跑 AI Agent 工作流的、经常被它到底跑完没有折磨的人。下面所有配置都可以直接复制我会把每一步的验证方法也写清楚。2. 前置准备TaoToken 接入与 Claude Code 环境确认在配 Hook 之前得先保证 Claude Code 本身能正常跑起来。如果你已经在用官方账号可以跳过接入部分直接看环境确认。如果你希望用统一的 API 入口来管理模型调用可以走 TaoToken 这套方案它的 Base URL 和 Key 管理比较集中后面配 Hook 时也不影响。先说清楚 TaoToken 是什么它是一个大模型 API 聚合入口提供统一的 Base URL 和 API KeyClaude Code、Cline、Codex 这类工具都可以通过它来调用模型。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 这个不加 UTM。你需要先去控制台创建一个 API Key路径在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 列表在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。拿到 Key 之后Claude Code 的接入方式是通过环境变量指定 Base URL 和 Key。在~/.zshrc或~/.bashrc里加上export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的Key然后source ~/.zshrc让配置生效。这里有个坑要注意Claude Code 读的是ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY这两个变量名别写成OPENAI_开头的否则它不认。改完之后用claude启动随便问一句你好能正常回复就说明接入通了。接下来确认 Hook 的运行环境。Hook 本质上是 Claude Code 在特定事件触发时去执行一条 shell 命令所以你需要第一确认 Claude Code 版本支持 Hook。Hook 是较新版本才有的能力用claude --version看一下版本太老就升级。第二确认本机有 Python 3因为后面通知脚本用 Python 写python3 --version能打印出来就行。第三确认配置文件位置。Claude Code 的用户级配置在~/.claude/settings.json如果这个文件不存在就手动创建注意它是标准 JSON不能有注释和尾逗号。关于模型选择如果你后面要跑长任务测试通知建议用一个稳定的模型 ID。在 TaoToken 的模型对话页面可以先验证模型是否可用 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。如果你打算长期用 Claude Code 做编码和 Agent 工作流可以考虑 Coding Plan入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它更适合高频编码场景。环境确认清单检查项命令期望结果Claude Code 版本claude --version能打印版本号且支持 HookPython 版本python3 --versionPython 3.x配置目录ls ~/.claude/存在 settings.json 或可创建API 接入claude后提问能正常回复这四步都过了再往下配 Hook 就不会因为环境问题卡住。很多人配 Hook 失败最后发现是 Claude Code 根本没接上模型Hook 触发了但任务本身没跑起来自然看不到通知。3. 可复制配置settings.json Hook 与 notify.py 脚本这一节是核心给你两份可以直接复制的配置一份是 Claude Code 的 Hook 配置一份是通知脚本。先讲 Hook 配置。Claude Code 的 Hook 写在~/.claude/settings.json里。它的结构是按事件名分组每个事件下有一个 matcher 和一组 hooks。我们要监听的是Notification事件它会在 Claude Code 需要你注意时触发包括任务完成等待输入idle_prompt和需要权限确认permission_prompt。配置片段如下{ hooks: { Notification: [ { matcher: , hooks: [ { type: command, command: python3 ~/.claude/skills/agent-notifier/notify.py } ] } ] } }这里matcher留空表示匹配所有 Notification 子类型。type固定是commandcommand就是事件触发时执行的命令。注意路径要写绝对路径或者用~展开Claude Code 执行时的工作目录不一定是你以为的那个写相对路径很容易找不到脚本。如果你已经有 settings.json不要整个覆盖把hooks这一段合并进去就行。合并后可以用python3 -m json.tool ~/.claude/settings.json校验一下 JSON 是否合法格式错了 Claude Code 会直接忽略整个配置而且不一定报错这是最容易踩的坑。接下来是通知脚本notify.py。它的职责是从 stdin 读取 Claude Code 传过来的 JSON解析出事件类型和消息然后分发到各个通知渠道。下面是一个精简但可用的版本只依赖 Python 标准库#!/usr/bin/env python3 import sys import json import subprocess import platform from concurrent.futures import ThreadPoolExecutor def parse_event(raw): try: data json.loads(raw) except Exception: return {platform: claude-code, event: unknown, message: raw.strip()} ntype data.get(notification_type, unknown) msg data.get(message, ) if ntype idle_prompt: msg Task completed - waiting for your input elif ntype permission_prompt: msg Permission required return {platform: claude-code, event: ntype, message: msg} def send_sound(event): system platform.system() if system Darwin: subprocess.run([afplay, /System/Library/Sounds/Glass.aiff], checkFalse) elif system Linux: subprocess.run([paplay, /usr/share/sounds/freedesktop/stereo/complete.oga], checkFalse) def send_macos_notification(event): if platform.system() ! Darwin: return script display notification {} with title Claude Code.format(event[message]) subprocess.run([osascript, -e, script], checkFalse) def dispatch(event): channels [send_sound, send_macos_notification] with ThreadPoolExecutor(max_workerslen(channels)) as pool: futures [pool.submit(ch, event) for ch in channels] for f in futures: try: f.result() except Exception as e: print(channel error: {}.format(e), filesys.stderr) def main(): raw sys.stdin.read() event parse_event(raw) dispatch(event) if __name__ __main__: main()把这段保存到~/.claude/skills/agent-notifier/notify.py然后chmod x给它执行权限。脚本里几个关键点解释一下parse_event把 Claude Code 的notification_type映射成人类可读的消息idle_prompt我特意改成了 Task completed因为原始消息 Claude is waiting for your input 不够直观dispatch用线程池并发调用多个渠道单个渠道失败只打到 stderr不影响其他渠道。如果你还想加 Telegram 通知在dispatch的 channels 里加一个函数用urllib.request发 POST 到 Telegram Bot API 即可同样不需要装任何第三方库。这样设计的好处是只要机器上有 Python脚本拿来就能用不用pip install任何东西。配置和脚本都就位后目录结构应该是这样~/.claude/ ├── settings.json └── skills/ └── agent-notifier/ └── notify.py4. 验证请求手动触发与真实任务测试配完不验证等于没配。这一节给你两种验证方式先手动模拟事件确认脚本本身没问题再跑真实任务确认 Hook 真的被触发。先做手动测试。Claude Code 的 Hook 是通过 stdin 把 JSON 传给脚本的所以我们可以直接模拟这个输入echo {notification_type:idle_prompt,message:test} | python3 ~/.claude/skills/agent-notifier/notify.py如果配置正确你应该能听到提示音macOS 上还会弹出系统通知。再测权限请求echo {notification_type:permission_prompt,message:needs permission} | python3 ~/.claude/skills/agent-notifier/notify.py这一步能过说明脚本解析和渠道分发都正常。如果没声音先检查afplay路径是否存在Linux 上检查paplay或aplay是否装了。手动测试通过后做真实任务测试。启动 Claude Code给它一个会跑一会儿的任务比如请帮我给当前目录下的 utils.py 里所有函数补上 docstring并逐个说明参数含义。任务开始后你故意切到别的窗口不要盯着终端。等它跑完Claude Code 会进入等待输入状态触发idle_prompt这时你应该收到通知。如果任务中途需要你授权某条命令会触发permission_prompt同样会通知你。验证成功的标志有三个一是提示音响了二是 macOS 通知中心出现了 Claude Code 标题的通知三是通知文案是 Task completed - waiting for your input 而不是原始的英文长句。三个都满足说明整条链路通了。如果你接了 Telegram还可以做一次远程验证让 Claude Code 跑一个稍长的任务然后人离开电脑用手机看 Telegram 有没有收到消息。这一步能验证任务完成即提醒的闭环在跨设备场景下也成立。这里补充一个调试技巧如果真实任务没触发通知但手动测试正常问题多半在 Hook 配置没被加载。可以在notify.py开头加一行日志把每次收到的 stdin 写到文件里with open(/tmp/claude-hook.log, a) as f: f.write(raw \n)然后跑一次任务看/tmp/claude-hook.log有没有内容。有内容说明 Hook 触发了问题在脚本内部没内容说明 Hook 根本没触发回去检查 settings.json 的 JSON 格式和路径。5. 常见报错排查401、local proxy failed 与 reading choices配 Hook 和接入 API 的过程中报错基本集中在几类。这一节按真实报错来对照排查你遇到哪个查哪个。第一类401 Unauthorized。这个几乎都是 Key 的问题。检查ANTHROPIC_API_KEY是不是复制时带了空格或者 Key 已经失效。如果你用的是 TaoToken去 API Keys 页面重新生成一个路径在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。注意环境变量改完要重新开一个终端或者source一下旧终端里还是老值。第二类local proxy failed或连接被拒绝。这类报错通常是 Base URL 写错了。确认ANTHROPIC_BASE_URL是https://taotoken.net/api不要多加斜杠或者写成别的路径。另外检查本机网络是否能正常访问该地址可以用curl -I https://taotoken.net/api看返回。如果公司网络有额外限制需要按公司规范处理不要用任何非正规的网络工具。第三类reading choices相关报错或者返回体解析失败。这通常出现在用 OpenAI 兼容格式去请求 Anthropic 接口或者反过来。Claude Code 走的是 Anthropic 协议Base URL 和请求格式要匹配。如果你在 Cline 或 Codex 里配注意它们的配置字段不一样Cline 用 MCP 配置时要写全 Base URL、Key、Model ID 三件套Codex 的auth.json里字段名也不同。三件套缺一个都会导致reading choices之类的解析错误。第四类Hook 触发了但没通知。分两种脚本报错和渠道失败。脚本报错看 stderrClaude Code 一般会把 Hook 的输出打出来渠道失败看是不是afplay路径不对或者 Telegram Token 填错。记住单个渠道失败不影响其他渠道所以如果声音响了但 Telegram 没收到问题只在 Telegram 那一路。第五类OAuth相关报错。如果你之前用官方账号登录过环境变量和 OAuth 登录可能冲突。Claude Code 优先用环境变量里的 Key如果 Key 无效又残留了 OAuth 状态就会报 OAuth 错误。解决办法是清理掉冲突的登录状态确保只用一种接入方式。排查顺序建议先确认 Claude Code 本身能正常对话排除 API 问题再手动测 notify.py排除脚本问题最后跑真实任务看 Hook 日志排除配置加载问题。这个顺序能帮你快速定位问题在哪一层而不是盲目改配置。6. 把通知封装成 SKILL长期用起来手动配一次 Hook 能用但如果你换机器、换项目或者同时用 Claude Code、Copilot CLI、Cursor 多个工具每次都重配一遍很烦。更好的做法是把它封装成一个 SKILL一次配置多平台复用。SKILL 的核心思路是统一事件模型。不同平台传过来的 JSON 格式不一样Claude Code 是{notification_type: idle_prompt, ...}Copilot CLI 是{hook_event_name: sessionEnd, ...}Cursor 是stop或afterFileEdit。notify.py 里加一层平台识别把这些格式统一解析成{platform, event, message}三元组再分发到通知渠道。这样新增一个平台只需要加一个解析分支通知渠道完全不用动。配置层面用一个notify-config.json管理渠道开关和凭据{ channels: { sound: { enabled: true }, macos_notification: { enabled: true }, telegram: { enabled: false, bot_token: , chat_id: } } }默认只开声音和系统通知Telegram、Email、Slack 这些需要凭据的默认关闭要用的时候再填。这样既安全又不会一上来就要求你配一堆东西。安装层面写一个setup.py做交互式引导自动检测你装了哪些 Agent 平台引导你填通知渠道然后自动往对应平台的配置文件里写 Hook。比如 Claude Code 写~/.claude/settings.jsonCursor 写它的hooks.json。写完发一条测试通知验证。这套封装下来你得到的不是一个一次性配置而是一个可迁移的通知能力。换电脑时把 skills 目录拷过去跑一次 setup 就行。对于长期跑 AI Agent 工作流的人来说这个投入很值——它把我是不是该去看一眼这个反复出现的念头变成了它跑完会主动叫我。最后说一个实测经验通知文案很重要。原始消息 Claude is waiting for your input 让人以为是它在等我改成 Task completed - waiting for your input 之后一眼就知道是任务跑完了。这种细节不影响功能但直接影响你用起来顺不顺手。配好之后你可以放心地把长任务丢给 Claude Code然后去干别的事等通知响了再回来。