
1. 这个提醒器到底在解决什么问题长时间跑代码生成任务的人都有一个共同的痛点你把一段复杂的重构需求丢给命令行里的 AI 编程助手它开始吭哧吭哧干活你盯着屏幕等了半分钟觉得无聊就切到浏览器看两眼新闻结果一回头发现它早就跑完了白白浪费了好几分钟。更糟的情况是它中途弹出一个确认提示等你输入而你完全没注意到任务就卡在那里一动不动。我平时用 Claude Code 和 Codex 这类命令行 AI 编程工具的频率很高几乎每天都要跑十几个任务。有些任务几秒钟就结束有些要跑三五分钟尤其是涉及大文件重构或者多轮推理的场景。一开始我的做法是傻等后来变成每隔十几秒切回来瞄一眼再后来干脆去干别的事但心里总惦记着“它跑完了没有”根本没法真正放松。这个完成提醒器要解决的核心问题就一个让 AI 编程助手在任务结束时主动通知你而不是你反复去查看它的状态。听起来简单但要做到“安心摸鱼”这个程度背后涉及几个关键的技术决策——怎么检测任务完成、用什么方式发通知、怎么区分“正常完成”和“等待输入”这两种状态。这几个问题不想清楚做出来的东西要么误报要么漏报反而更让人焦虑。适合读这篇内容的人日常使用命令行 AI 编程工具的开发者、想给自己工作流加自动化提醒的效率爱好者、以及任何对终端任务监控感兴趣的人。不需要很深的编程基础但至少要能看懂基本的 Shell 命令和 Python 脚本。2. 整体设计思路与方案选型2.1 为什么不做成插件而是做成独立监控进程最开始我考虑过给 Claude Code 写一个插件或者 hook直接在它的生命周期里挂回调。但实际调研后发现两个问题一是不同工具的插件机制差异很大Claude Code 有 hooks 机制Codex 的扩展方式又不一样想统一处理很麻烦二是插件方案和工具本身耦合太紧工具一升级就可能失效。所以最终选了一个更“笨”但更稳的方案独立监控进程。它的工作逻辑很简单——定期检查目标进程是否还在运行、终端输出有没有新变化一旦判断任务结束就触发通知。这个方案的好处是跟具体工具解耦不管是 Claude Code、Codex 还是以后换别的工具只要它是在终端里跑的进程监控逻辑都能复用。代价是检测精度不如原生 hook。原生 hook 能精确知道“任务完成”这个事件而独立监控只能通过间接信号去推断。这就引出了下一个关键决策。2.2 检测任务完成的三种信号及取舍判断一个终端里的 AI 任务是否结束我梳理了三种可用的信号检测信号原理优点缺点进程存活检测检查目标 PID 是否还在实现简单零误报无法区分“完成”和“等待输入”输出静默检测监控终端输出超过 N 秒无新内容则判定结束能覆盖大多数场景阈值难调长推理会误判提示符匹配检测终端是否回到 shell 提示符最准确依赖终端环境跨平台麻烦我最终采用的是进程存活 输出静默的组合方案。逻辑是这样的先确认目标进程还在如果进程消失了那肯定是结束了如果进程还在但输出已经静默超过设定阈值我用的默认值是 8 秒那就判定为“可能完成或等待输入”触发一个较弱的提醒。为什么是 8 秒这是我实测下来的经验值。AI 编程工具在正常推理时输出间隔一般在 1 到 3 秒之间偶尔有长推理会到 5 秒左右。设成 8 秒能过滤掉绝大多数正常的推理间隙又不会让提醒来得太晚。当然这个值可以配置后面会讲怎么调。提示如果你用的模型推理特别慢或者网络波动大8 秒可能不够建议先观察几次正常任务的输出间隔再定阈值。2.3 通知方式的选择通知方式我试过好几种最后保留了三种按打扰程度递增终端标题闪烁最轻量不打断当前操作适合你就在电脑前但没看终端的情况系统桌面通知中等打扰会弹一个通知卡片适合你切到了别的窗口声音提醒最强打扰适合你离开工位去倒水的情况这三种可以组合使用也可以单独开关。我自己的配置是桌面通知常开声音只在任务超过 60 秒时才触发——短任务没必要响长任务才值得提醒。3. 核心实现细节与实操要点3.1 进程监控的实现监控进程的核心是拿到目标进程的 PID然后定期检查它是否存活。在 Linux 和 macOS 上用ps -p PID就能判断在 Windows 上要用tasklist配合过滤。为了跨平台我用 Python 的psutil库来统一处理它封装了各平台的进程查询接口。拿到 PID 的方式有两种。一种是你启动任务时手动记录比如claude-code ... echo $!把 PID 存到一个临时文件里。另一种是自动发现通过进程名或者命令行参数去匹配。我推荐第一种虽然麻烦一点但更可靠自动发现容易误匹配到别的进程。import psutil import time def is_process_alive(pid): try: proc psutil.Process(pid) return proc.is_running() and proc.status() ! psutil.STATUS_ZOMBIE except psutil.NoSuchProcess: return False这段代码看起来简单但有个坑要注意进程变成僵尸状态zombie时is_running()可能还返回 True所以必须额外检查status()。我一开始没加这个判断结果任务明明结束了却一直不触发提醒排查了半天才发现是僵尸进程的问题。3.2 输出静默检测的实现输出静默检测比进程监控复杂一些因为要实时读取终端的输出。这里有个关键问题AI 编程工具的输出是写到它自己的 stdout 的而我们的监控进程是独立的怎么拿到它的输出我的做法是重定向 文件监控。启动任务时把它的输出同时写到终端和一个日志文件claude-code 重构这个模块 21 | tee /tmp/ai_task.log然后用监控进程定期检查这个日志文件的最后修改时间。如果超过阈值没有更新就判定为静默。import os def get_last_modified(filepath): return os.path.getmtime(filepath) def is_output_silent(filepath, threshold8): last_mod get_last_modified(filepath) return (time.time() - last_mod) threshold用tee的好处是输出既能在终端看到又能被监控进程读取两不耽误。这里要注意日志文件的路径要固定别用随机名否则监控进程找不到。注意有些工具的输出是带缓冲的可能攒一批才写一次这会导致静默检测误判。如果遇到这种情况可以在启动命令前加stdbuf -oL强制行缓冲。3.3 通知模块的实现桌面通知在 macOS 上用osascriptLinux 上用notify-sendWindows 上用win10toast或者 PowerShell 的 BurntToast 模块。我写了一个统一的接口根据当前系统自动选择import platform import subprocess def send_notification(title, message): system platform.system() if system Darwin: subprocess.run([osascript, -e, fdisplay notification {message} with title {title}]) elif system Linux: subprocess.run([notify-send, title, message]) elif system Windows: subprocess.run([powershell, -Command, fNew-BurntToastNotification -Text {title}, {message}])声音提醒更简单macOS 用afplay播放系统音效Linux 用aplayWindows 用winsound。终端标题闪烁则是往终端发送 ANSI 转义序列这个稍微 tricky 一点因为要找到正确的终端设备。3.4 状态区分完成还是等待输入这是整个项目里最难的部分。进程还在、输出静默了可能是任务完成了也可能是工具在等你确认某个操作。这两种情况需要不同的提醒策略——完成了就通知你收工等待输入则要提醒你赶紧去处理。我用的区分方法是检测输出内容的最后几行。AI 编程工具在等待输入时通常会在最后输出一个提示符或者问句比如Do you want to proceed? (y/n)或者Continue?。而正常完成时最后输出的一般是结果总结或者直接回到 shell 提示符。def check_waiting_for_input(logfile): with open(logfile, r) as f: lines f.readlines() last_lines .join(lines[-5:]).lower() keywords [(y/n), continue?, proceed?, confirm, 请输入, 是否继续] return any(kw in last_lines for kw in keywords)这个方法不是 100% 准确因为工具的提示语可能变化但实测下来覆盖了我常用的几个工具。如果匹配到等待输入的信号通知内容会变成“任务等待你的输入”而不是“任务已完成”。实操心得关键词列表要定期更新。工具升级后提示语可能变我一般每隔几周会看一眼最近的日志把新的提示语加进去。4. 完整实操流程与配置方法4.1 环境准备与依赖安装先把基础环境搭起来。需要 Python 3.8 以上以及psutil库pip install psutilWindows 用户额外装win10toastpip install win10toastLinux 用户确认notify-send可用一般桌面环境都自带没有的话装libnotify-binsudo apt install libnotify-binmacOS 用户不需要额外装东西osascript和afplay都是系统自带的。4.2 启动任务并记录 PID写一个包装脚本把启动任务和记录 PID 合并成一步#!/bin/bash # run_with_monitor.sh TASK_LOG/tmp/ai_task_$(date %s).log PID_FILE/tmp/ai_task.pid # 启动任务输出同时写终端和日志 $ 21 | tee $TASK_LOG TASK_PID$! # 记录 PID 和日志路径 echo $TASK_PID $PID_FILE echo $TASK_LOG /tmp/ai_task_log_path # 启动监控进程 python3 monitor.py $TASK_PID $TASK_LOG wait $TASK_PID用的时候这样调用./run_with_monitor.sh claude-code 帮我重构 utils 模块这样任务在跑的同时监控进程也在后台盯着任务一结束就发通知。4.3 监控进程的完整实现把前面几节的代码整合起来加上主循环import sys import time import psutil import platform import subprocess def is_process_alive(pid): try: proc psutil.Process(pid) return proc.is_running() and proc.status() ! psutil.STATUS_ZOMBIE except psutil.NoSuchProcess: return False def is_output_silent(logfile, threshold8): import os try: last_mod os.path.getmtime(logfile) return (time.time() - last_mod) threshold except FileNotFoundError: return False def check_waiting_for_input(logfile): try: with open(logfile, r) as f: lines f.readlines() last_lines .join(lines[-5:]).lower() keywords [(y/n), continue?, proceed?, confirm, 请输入, 是否继续] return any(kw in last_lines for kw in keywords) except FileNotFoundError: return False def send_notification(title, message): system platform.system() if system Darwin: subprocess.run([osascript, -e, fdisplay notification {message} with title {title}]) elif system Linux: subprocess.run([notify-send, title, message]) elif system Windows: subprocess.run([powershell, -Command, fNew-BurntToastNotification -Text {title}, {message}]) def main(): pid int(sys.argv[1]) logfile sys.argv[2] notified False start_time time.time() while True: if not is_process_alive(pid): if not notified: send_notification(任务完成, AI 编程任务已结束) notified True break if is_output_silent(logfile): if not notified: if check_waiting_for_input(logfile): send_notification(等待输入, 任务需要你的确认) else: send_notification(可能完成, 输出已静默请检查) notified True else: notified False time.sleep(2) if __name__ __main__: main()这个实现里有个细节notified标志位用来防止重复通知。输出静默时发一次通知如果之后又有新输出说明任务还在跑就把标志位重置下次静默时再通知。这样避免了同一个任务反复弹通知。4.4 参数调优与个性化配置默认的 8 秒静默阈值不一定适合所有人。调优的方法是先跑几个典型任务记录每次输出间隔的最大值然后把这个值乘以 1.5 到 2 倍作为阈值。我自己的配置是这样的参数默认值我的配置说明静默阈值8 秒10 秒我的任务推理时间偏长检查间隔2 秒2 秒再短没必要再长反应慢声音提醒阈值无60 秒短任务不响长任务才响等待输入关键词内置列表自定义扩展加了几个我常用工具的提示语声音提醒的阈值逻辑是在监控进程里加一个判断任务运行时间超过 60 秒才播放声音否则只发桌面通知。elapsed time.time() - start_time if elapsed 60: play_sound()5. 常见问题与排查技巧实录5.1 通知不触发或触发太晚这是最常见的问题原因通常有三个。第一个是 PID 记录错了比如用了管道导致$!拿到的是tee的 PID 而不是任务的 PID。解决办法是在启动脚本里仔细确认 PID 来源必要时用pgrep按进程名反查。第二个是日志文件路径不对监控进程读不到文件getmtime一直返回旧值或者抛异常。排查方法是手动ls -la看一下日志文件是否存在、修改时间是否在更新。第三个是静默阈值设得太大任务早就结束了但监控还在等。这个直接调小阈值就行但要注意别调太小导致误报。5.2 误报任务还在跑却提示完成误报的根源是输出静默检测把正常的推理间隙当成了任务结束。AI 模型在生成长文本时有时候会思考好几秒才吐下一个 token如果阈值设成 5 秒就很容易误报。我的解决办法是双重确认第一次检测到静默时先不通知等再过 3 秒重新检查如果还是静默才发通知。这样能把大部分瞬时静默过滤掉。if is_output_silent(logfile): time.sleep(3) if is_output_silent(logfile): # 确认静默发通知代价是通知会晚 3 秒但比起误报带来的焦虑这 3 秒完全值得。5.3 跨平台兼容性问题Windows 上的坑最多。psutil在 Windows 上查进程状态没问题但tee命令不存在要用 PowerShell 的Tee-Object替代。桌面通知也要装额外的模块。我建议 Windows 用户直接用 WSL 跑这套方案省去很多兼容性麻烦。macOS 上主要问题是osascript的通知权限第一次运行会弹权限请求要点允许。如果没弹或者误点了拒绝要去系统设置的“通知”里手动给终端或者 Python 开权限。Linux 上的问题是不同桌面环境通知机制不一样GNOME 和 KDE 都能用notify-send但一些轻量级桌面环境可能没装。装个libnotify-bin基本能解决。5.4 常见问题速查表现象可能原因排查方法解决方案完全不通知PID 错误检查 PID 文件内容修正启动脚本的 PID 获取通知太晚阈值过大观察输出间隔调小静默阈值频繁误报阈值过小看日志确认任务状态调大阈值 双重确认通知重复弹标志位逻辑错误检查 notified 变量修正重置逻辑Windows 无通知缺依赖检查 win10toast安装依赖或用 WSL声音不响音频命令缺失手动执行播放命令装对应音频工具实操心得我建议第一次部署时把检查间隔调到 1 秒、阈值调到 5 秒这样能快速看到效果确认整条链路通了之后再调回正常值。先跑通再调优比一上来就追求完美配置效率高得多。6. 进阶玩法与扩展思路6.1 多任务并行监控同时跑多个 AI 任务时每个任务都要有独立的 PID 和日志文件。我把监控进程改成了支持多任务的形式用一个配置文件列出所有正在跑的任务{ tasks: [ {pid: 12345, log: /tmp/task1.log, name: 重构任务}, {pid: 12346, log: /tmp/task2.log, name: 测试生成} ] }监控进程定期读这个配置对每个任务独立判断。通知里带上任务名这样你一眼就知道是哪个任务完成了。6.2 通知内容带上任务摘要光说“任务完成”信息量太少如果能带上任务的关键信息就更好了。我的做法是从日志文件的最后几行提取摘要比如最后修改的文件名、生成的代码行数等拼到通知内容里。def extract_summary(logfile): with open(logfile, r) as f: lines f.readlines() # 取最后 3 行非空内容 content [l.strip() for l in lines if l.strip()][-3:] return | .join(content)[:100]这样通知就变成了“任务完成重构了 utils.py新增 45 行删除 12 行”比干巴巴的“完成”有用得多。6.3 与手机联动如果你经常离开电脑桌面通知就看不到了。可以把通知转发到手机上方法是用一个简单的 HTTP 请求推送到手机的通知服务。具体用哪个服务这里不展开思路就是监控进程检测到完成后除了发桌面通知再发一个网络请求到你的手机。这个功能我用的不多因为大部分时候我就在电脑附近但如果你有远程监控的需求值得加上。6.4 统计与回顾跑了一段时间后我加了一个简单的统计功能记录每个任务的开始时间、结束时间、运行时长存到一个 CSV 文件里。周末回顾一下能看出哪些任务耗时最长、哪些工具效率最高。这个数据对优化工作流很有参考价值。import csv from datetime import datetime def log_task_stats(name, start, end): with open(task_stats.csv, a, newline) as f: writer csv.writer(f) writer.writerow([name, datetime.fromtimestamp(start).isoformat(), datetime.fromtimestamp(end).isoformat(), round(end - start, 1)])这个 CSV 用 Excel 或者 Numbers 打开就能看不需要额外的可视化工具。7. 我踩过的几个坑和最终体会第一个坑是过度依赖进程存活检测。一开始我觉得进程没了就是任务结束了简单可靠。但实际用下来发现AI 编程工具经常会有子进程主进程结束了子进程还在跑或者反过来。后来改成进程存活和输出静默双重判断才稳定下来。第二个坑是通知太频繁。刚开始我把静默阈值设成 3 秒结果任务正常推理时也频繁弹通知烦得我直接关掉了。阈值这个东西真的不能拍脑袋定一定要观察实际任务的输出模式再定。第三个坑是忽略了等待输入的情况。有次我跑一个需要确认的任务去倒了杯水回来发现它卡在确认提示那里已经五分钟了。从那以后我才加了等待输入的检测逻辑虽然不完美但至少能提醒我去看一眼。这套东西我用了几个月最大的体会是工具的价值不在于多复杂而在于能不能让你真正放心地去做别的事。一个简单的完成提醒器解决的是心理层面的焦虑——你知道任务结束时会有人告诉你所以你可以安心地去摸鱼、去开会、去干别的。这种“不用惦记”的感觉比省下来的那几分钟时间值钱得多。如果你也想搭一套我的建议是从最简版本开始先只做进程存活检测加桌面通知跑通了再逐步加输出静默、等待输入检测、声音提醒这些功能。别一上来就追求大而全能解决 80% 场景的简单方案比一个复杂但跑不起来的完美方案有用得多。