1. Claude Code 长任务跑起来后,为什么你总在盯屏
Claude Code 这类终端里的编码 Agent,最舒服的用法是丢一个长任务进去,比如重构一个模块、批量补测试、跑一轮依赖升级,然后你去干别的事。但现实往往是:你每隔两分钟切回终端看一眼,怕它卡在权限确认上,怕它报错退出,怕它其实早就跑完了你还在傻等。这个「盯屏焦虑」在本地开发和自动化脚本场景里特别明显,因为 Claude Code 默认不会主动告诉你状态变化,它只会在终端里默默输出。
我试过把 Claude Code 挂在 tmux 里跑,结果还是得手动tmux attach去看。问题的本质是:Claude Code 有一套 hooks 机制,能在关键事件触发时执行你配置的命令,但默认没人去接这些事件。你要做的,就是给这些事件挂上一个通知脚本,让任务完成、需要确认、执行失败这些时刻主动推送到你眼前。
这篇要解决的就是这件事:给 Claude Code 装一个通知监控,覆盖 Windows Toast 本地弹窗和 Webhook 远程推送两条路。同时把模型通道统一到 TaoToken 的 Key 上,这样无论你切哪个模型,通知配置都不用改。适合谁?适合每天用 Claude Code 跑长任务、又不想一直守着终端的本地开发者,以及把 Claude Code 塞进自动化流水线、需要感知每一步结果的脚本党。
先说清楚 Claude Code 的 hooks 能接哪些事件,这决定了你的通知能覆盖多细。常用的有这几类:Stop在 Claude 完成一轮响应时触发,对应「任务完成」;PermissionRequest在需要你批准某个操作时触发,对应「需要确认」;PostToolUse可以匹配具体工具名,比如EnterPlanMode、AskUserQuestion,对应「等待输入」;还有TeammateIdle这类团队协作场景的空闲通知。把这些事件映射成通知类型,你就能做到不盯屏也知道 Claude 现在卡在哪一步。
通知的落地方式分两种。一种是本地弹窗,Windows 上用 PowerShell 调 Toast API,Mac 上用osascript或terminal-notifier,优点是零依赖、即时;另一种是 Webhook,把事件 POST 到一个 HTTP 端点,可以推到飞书、钉钉、企业微信或者你自己的服务,优点是跨设备、可留痕。两种可以同时配,本地弹窗负责即时提醒,Webhook 负责远程和归档。
这里有个容易被忽略的点:Claude Code 的 hooks 是在 harness 层执行的,它不会在执行 hook 前再问你一次权限。也就是说,你写进settings.json的 hook 命令会被直接执行。所以 hook 命令里只应该调用你自己信任的通知脚本,别塞来源不明的命令。这一点后面排障章节还会展开。
2. TaoToken 统一 Key 通道的前置准备
在配通知之前,先把模型通道理顺。原因很实际:通知脚本本身不关心你用哪个模型,但你的 Claude Code 会。如果你今天用这个 Key、明天换那个端点,settings.json里的环境变量和 hook 配置就会跟着乱。把模型访问统一到 TaoToken 的 Key 通道上,好处是 Base URL 和 Key 固定,通知配置写一次就不用动。
TaoToken 在这里扮演的是统一入口的角色:你拿一个 Key,就能在 Claude Code 里访问多种模型,不用为每个模型单独维护一套凭证。对通知监控这个场景来说,这意味着你的settings.json里模型相关的部分和 hooks 部分是解耦的,改通知不会碰到模型配置,改模型也不会影响通知。
前置准备分三步。第一步,拿到 API Key。打开 https://taotoken.net/api-keys 创建或复制你的 Key,注意这个页面是控制台里的密钥管理入口,Key 只显示一次,复制后自己存好。第二步,确认 Base URL。TaoToken 的 API 端点是 https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为ANTHROPIC_BASE_URL的值。第三步,选一个 Model ID。Claude Code 走的是 Anthropic 兼容协议,Model ID 填你实际要用的模型标识,比如claude-sonnet-4-5这类,具体以你账号下可用的为准。
把这三件套写进环境变量,Claude Code 启动时就会读。Windows 上可以在 PowerShell 里临时设置,也可以写进系统环境变量;Mac/Linux 上写进~/.zshrc或~/.bashrc。三件套是:Base URL、Key、Model ID,缺一不可。很多人只配了 Key 忘了 Base URL,结果请求打到默认端点上去,报 401 或者连接失败,这类问题在排障章节会具体讲。
如果你用的是 Claude Code 的配置文件方式,可以在~/.claude/settings.json里通过env字段注入,这样比系统环境变量更可控,也方便和 hooks 放在同一个文件里管理。下面给一个最小示例,注意 Key 不要明文提交到 Git:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的TaoToken密钥", "ANTHROPIC_MODEL": "claude-sonnet-4-5" } }这里ANTHROPIC_AUTH_TOKEN就是你的 TaoToken Key,ANTHROPIC_BASE_URL固定为https://taotoken.net/api,ANTHROPIC_MODEL填你要用的 Model ID。三个值确认无误后,Claude Code 的模型请求就走通了。这一步做完,再去配通知,你的注意力就只需要放在 hooks 上。
顺便说一句,如果你还没决定长期用哪个模型,可以先在 https://taotoken.net/models 里对话验证一下,确认模型可用、响应正常,再写进配置。验证模型和配通知是两件独立的事,但顺序上建议先验证模型通道,否则通知配好了、模型却报错,你会分不清是哪一层的问题。
3. 可复制的通知监控配置:hooks 加 Webhook 参数
这一节是核心,给你可以直接抄的配置。整体结构是:一个通知脚本负责实际发送,settings.json里的 hooks 负责在事件触发时调用这个脚本。脚本同时支持本地 Toast 和 Webhook 两种输出,通过参数切换。
先建目录结构。Claude Code 的 skill 约定放在~/.claude/skills/下,我们建一个notify-monitor:
~/.claude/skills/notify-monitor/ ├── SKILL.md ├── scripts/ │ ├── notify.ps1 # Windows Toast + Webhook │ └── notify.sh # Mac/Linux 版本 └── assets/ └── icon.png # 可选,通知图标Windows 版notify.ps1的核心逻辑:接收-Type、-Message、-Sound、-Webhook参数,先调 Windows Toast API 弹本地通知,如果传了-Webhook就再发一个 POST。下面是一个精简可用的版本:
param( [Parameter(Mandatory=$true)][string]$Type, [Parameter(Mandatory=$true)][string]$Message, [switch]$Sound, [string]$Webhook = "", [int]$Duration = 5 ) # 本地 Toast 通知 $titleMap = @{ "complete" = "任务完成" "confirm" = "需要确认" "wait" = "等待输入" "milestone" = "关键节点" "error" = "执行失败" } $title = $titleMap[$Type] if (-not $title) { $title = "Claude Code 通知" } $toastParams = @{ Text = $Message Title = $title AppLogo = "$PSScriptRoot/../assets/icon.png" } if ($Sound) { $toastParams.Sound = "Notification.Default" } # 使用 BurntToast 模块(需先 Install-Module BurntToast) if (Get-Module -ListAvailable -Name BurntToast) { Import-Module BurntToast New-BurntToastNotification @toastParams } else { # 无模块时退化为 msg 命令 msg * "$title : $Message" } # Webhook 推送 if ($Webhook -ne "") { $payload = @{ type = $Type message = $Message time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } | ConvertTo-Json -Compress try { Invoke-RestMethod -Uri $Webhook -Method Post -Body $payload ` -ContentType "application/json" -TimeoutSec 10 } catch { Write-Warning "Webhook 推送失败: $_" } }Mac/Linux 版notify.sh用osascript或notify-send,Webhook 部分用curl:
#!/usr/bin/env bash TYPE="$1"; MESSAGE="$2"; WEBHOOK="$3" case "$TYPE" in complete) TITLE="任务完成" ;; confirm) TITLE="需要确认" ;; wait) TITLE="等待输入" ;; milestone) TITLE="关键节点" ;; error) TITLE="执行失败" ;; *) TITLE="Claude Code 通知" ;; esac # 本地通知 if command -v osascript >/dev/null 2>&1; then osascript -e "display notification \"$MESSAGE\" with title \"$TITLE\"" elif command -v notify-send >/dev/null 2>&1; then notify-send "$TITLE" "$MESSAGE" fi # Webhook if [ -n "$WEBHOOK" ]; then curl -s -X POST "$WEBHOOK" \ -H "Content-Type: application/json" \ -d "{\"type\":\"$TYPE\",\"message\":\"$MESSAGE\",\"time\":\"$(date '+%F %T')\"}" \ --max-time 10 || echo "Webhook 推送失败" >&2 fi脚本有了,接下来是settings.json里的 hooks 配置。这是最关键的一段,直接决定哪些事件会触发通知。下面这份配置覆盖了完成、确认、等待输入、失败四类场景,Webhook 地址用占位符,你替换成自己的:
{ "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "powershell -ExecutionPolicy Bypass -File ~/.claude/skills/notify-monitor/scripts/notify.ps1 -Type complete -Message 'Claude 任务已完成' -Sound -Webhook 'https://your-webhook.example.com/claude'" } ] } ], "PermissionRequest": [ { "matcher": "", "hooks": [ { "type": "command", "command": "powershell -ExecutionPolicy Bypass -File ~/.claude/skills/notify-monitor/scripts/notify.ps1 -Type confirm -Message 'Claude 需要你的确认' -Sound -Webhook 'https://your-webhook.example.com/claude'" } ] } ], "PostToolUse": [ { "matcher": "EnterPlanMode", "hooks": [ { "type": "command", "command": "powershell -ExecutionPolicy Bypass -File ~/.claude/skills/notify-monitor/scripts/notify.ps1 -Type confirm -Message '请审批 Claude 的计划' -Sound" } ] }, { "matcher": "AskUserQuestion", "hooks": [ { "type": "command", "command": "powershell -ExecutionPolicy Bypass -File ~/.claude/skills/notify-monitor/scripts/notify.ps1 -Type wait -Message 'Claude 需要你的输入' -Sound" } ] } ] } }几个参数说明。matcher为空字符串表示匹配该事件的所有情况;PostToolUse的matcher填具体工具名,比如EnterPlanMode、AskUserQuestion,只有这些工具被调用时才触发。-Webhook参数只在需要远程推送的事件上加,本地弹窗类的事件可以不加,减少网络请求。-Sound控制是否播放提示音,需要安静环境时去掉即可。
如果你不想手动编辑 JSON,Claude Code 提供了/update-config这类交互式配置入口,可以用自然语言描述你要加的 hooks,让它帮你写进settings.json。但无论哪种方式,最终落到文件里的结构就是上面这样,理解结构比记住命令更重要。
Webhook 端点的选择上,飞书、钉钉、企业微信的群机器人 Webhook 都能直接收 JSON,但它们的字段格式不完全一样。上面脚本发的是通用 JSON,如果你要对接特定平台,需要在脚本里把 payload 改成对应格式。比如飞书群机器人要的是{"msg_type":"text","content":{"text":"..."}},这个转换放在脚本里做,hooks 配置不用动。
4. 验证一次任务完成与失败告警
配置写完不验证,等于没配。这一节给你两个可复现的验证动作,一个测完成通知,一个测失败通知,都不需要真的跑一个长任务。
先测脚本本身能不能弹通知。在 PowerShell 里直接调:
powershell -ExecutionPolicy Bypass -File ~/.claude/skills/notify-monitor/scripts/notify.ps1 -Type complete -Message "测试通知:任务完成" -Sound如果 Windows 右下角弹出「任务完成」的通知,说明本地 Toast 通了。如果没弹,先看 Windows 设置里的通知开关,再看 BurntToast 模块是否安装。这一步是隔离验证,排除了 Claude Code 的干扰。
再测 Webhook。把-Webhook参数指向你的端点,跑一次:
powershell -ExecutionPolicy Bypass -File ~/.claude/skills/notify-monitor/scripts/notify.ps1 -Type error -Message "测试通知:执行失败" -Webhook "https://your-webhook.example.com/claude"去你的 Webhook 接收端看有没有收到这条 JSON,字段应该是type、message、time三个。收到就说明远程通道通了。
脚本层验证完,再验证 hooks 是否真的被 Claude Code 触发。启动 Claude Code,随便给它一个会触发Stop的简单任务,比如「列出当前目录的文件」。任务完成后,你应该收到「任务已完成」的通知。如果没收到,检查settings.json的路径是不是~/.claude/settings.json,以及 hook 命令里的脚本路径是不是绝对路径或正确的~展开路径。
失败告警的验证稍微绕一点,因为 Claude Code 正常跑不会主动报错。你可以构造一个会失败的操作,比如让它执行一个不存在的命令,或者在 hook 里临时把-Type改成error来模拟。更稳妥的做法是单独写一个测试 hook,只在手动触发时调用notify.ps1 -Type error,确认失败通知的文案和声音符合预期,再把它接到真实事件上。
验证通过后,你会看到这样的结果:Claude Code 在后台跑长任务,任务完成时你手机上的群机器人收到一条消息,同时电脑弹出 Toast;需要你确认权限时,通知类型是confirm,你能立刻切回去处理;如果某一步失败,error类型的通知会带上失败信息。整个过程你不需要盯着终端。
这里补一个实用技巧:通知消息里不要塞敏感信息。比如不要把文件绝对路径、命令原文、密钥片段写进-Message,因为 Toast 通知会进 Windows 通知中心,其他应用可能读到。用通用描述,比如「任务已完成」「需要确认」,具体细节回终端看。这个习惯在团队协作或共享电脑上尤其重要。
5. 常见报错排查:401、local proxy failed、reading choices
通知监控配好后,报错通常来自两层:模型通道层和 hooks 执行层。分开排查,别混在一起看。
401 Unauthorized。这个几乎都是 Key 或 Base URL 的问题。先确认ANTHROPIC_AUTH_TOKEN是你的 TaoToken Key,没有多余空格;再确认ANTHROPIC_BASE_URL是https://taotoken.net/api,注意结尾没有斜杠、没有多余路径。如果 Key 是对的但还报 401,去 https://taotoken.net/api-keys 看这个 Key 是否被禁用或额度耗尽。还有一种情况是环境变量没生效,Claude Code 读的是旧值,重启终端或重新加载配置文件。
local proxy failed / connection refused。这类报错说明请求根本没发出去,或者发到了一个本地代理端口。检查你的环境里有没有残留的HTTP_PROXY、HTTPS_PROXY设置指向一个已经关掉的本地端口。Claude Code 会读这些环境变量,如果代理不通就会报 local proxy failed。清掉这些变量,或者确认代理服务在运行。注意这里说的是环境变量层面的代理配置,不是让你去搭什么通道,只是排查残留配置。
reading choices / unexpected response shape。这个报错通常出现在响应格式不符合预期时,常见原因是 Base URL 指向了一个不兼容 Anthropic 协议的端点,或者 Model ID 填错了。确认ANTHROPIC_BASE_URL是 TaoToken 的 API 地址,ANTHROPIC_MODEL是你账号下真实可用的 Model ID。如果 Model ID 写了一个不存在的名字,服务端可能返回一个结构不同的错误响应,客户端解析时就报 reading choices 之类的错。
OAuth 相关报错。如果你之前用 OAuth 方式登录过 Claude Code,配置里可能残留了 OAuth 凭证,和现在的 Key 方式冲突。检查~/.claude/下有没有旧的凭证文件,必要时清理掉,让 Claude Code 走ANTHROPIC_AUTH_TOKEN这条路径。OAuth 和 Key 两种方式不要混用。
hooks 不触发。如果模型通道正常,但通知不弹,问题在 hooks。先确认settings.json的 JSON 语法正确,可以用python -m json.tool ~/.claude/settings.json校验。再确认 hook 命令里的脚本路径存在,Test-Path一下。Windows 上路径分隔符和~展开容易出问题,建议在 hook 命令里用绝对路径,比如C:/Users/你的用户名/.claude/skills/notify-monitor/scripts/notify.ps1。另外,PostToolUse的matcher大小写敏感,工具名要写对。
通知弹了但没声音。检查-Sound参数是否传了,以及 Windows 的通知声音设置。BurntToast 的Sound参数支持Notification.Default、Notification.Looping.Alarm等值,如果系统静音或专注助手开着,声音会被抑制。
Webhook 收不到。先在脚本层用curl或Invoke-RestMethod单独测端点,确认端点可达。再看脚本里的-Webhook参数有没有传对,URL 有没有被 shell 转义。如果端点要求特定 header 或签名,需要在脚本里补上。超时设 10 秒,避免 hook 卡住影响 Claude Code 主流程。
排查顺序建议:先隔离脚本(手动跑 notify.ps1),再隔离模型(手动发一个请求),最后看 hooks 配置。三层分开测,比一上来就怀疑 Claude Code 本身高效得多。
6. 把通知监控接进你的日常流程
配置跑通之后,通知监控的价值在于让你敢把长任务丢出去。你可以根据任务类型调整通知粒度:短任务只留Stop完成通知;涉及权限操作的任务加上PermissionRequest;需要你中途决策的任务加上AskUserQuestion。通知太频繁会烦,太少又失去意义,按自己的节奏调。
Webhook 那条路可以玩得更开。把事件推到自己的服务,就能做任务历史记录、失败率统计、甚至触发下一步自动化。比如任务完成后自动跑测试,失败时自动开一个 issue。这些都在 Webhook 接收端做,Claude Code 这边只负责发事件。
模型通道统一在 TaoToken 的 Key 上之后,你换模型只需要改ANTHROPIC_MODEL一个值,通知配置完全不用动。这种解耦在长期使用里省心很多。如果你还在选长期用的模型,可以去 https://taotoken.net/models 对话验证;如果打算把 Claude Code 接进更重的编码和 Agent 流程,可以看看 https://taotoken.net/coding-plan 的长期方案;接入细节和参数说明在 https://taotoken.net/doc 里有完整文档。
最后留一个我踩过的坑:hook 命令里不要写会阻塞很久的操作。通知脚本要快速返回,Webhook 超时设短一点,否则 Claude Code 会等 hook 执行完才继续,长任务反而被拖慢。通知是辅助,别让它成为新的瓶颈。