远程监控 Claude Code 与 Codex:日志轮询、系统通知与 SSH 方案

📅 发布时间:2026/10/19 3:55:59
远程监控 Claude Code 与 Codex:日志轮询、系统通知与 SSH 方案
1. 远程盯梢这件事到底卡在哪个环节先说一个我观察到的普遍现象很多人用 Claude Code 或者 Codex 这类命令行 AI 编程助手刚开始都特别兴奋觉得效率起飞了。但用了一两周之后一个很尴尬的问题就冒出来了——你没法一直坐在电脑前盯着它跑。这不是矫情。这类工具的工作模式跟传统的 IDE 补全完全不一样。传统补全是你敲一个字符它给一个建议你始终在交互循环里。而 Claude Code 和 Codex 这类工具你给它一个任务描述它会自己规划步骤、读文件、改代码、跑测试中间可能持续几分钟到十几分钟甚至更久。你发起任务之后理论上可以去做别的事但心里总悬着一件事它跑到哪了有没有卡住有没有改错文件有没有在某个循环里出不来我自己的经历是这样的有一次让 Claude Code 重构一个模块的目录结构涉及十几个文件的移动和引用更新。发起之后我去倒了杯水回来发现它卡在一个 import 路径的循环修正里反复改同一个文件改了七八轮。如果当时我能早点看到它的执行状态直接中断调整指令就不会浪费那十几分钟。所以这个问题的本质不是怎么远程控制电脑而是怎么在不盯着终端的情况下及时获取 AI 编程助手的执行进度和关键状态变化。这个需求拆开来看包含三个层面第一你需要知道它当前在做什么进度感知第二你需要知道它有没有出问题异常感知第三你需要在必要时能干预远程操作。三个层面的实现难度和方案选择完全不同下面逐个拆。注意本文讨论的所有方案都基于你本机已经正常运行 Claude Code 或 Codex 的前提不涉及任何网络穿透或代理配置。核心思路是利用这些工具自身的日志输出能力和系统级的通知机制。2. 先搞清楚 Claude Code 和 Codex 各自吐出了什么在动手搭方案之前必须先把这两个工具的运行特征摸清楚。它们虽然都是命令行 AI 编程助手但输出行为和日志结构有差异直接决定了你能用什么方式去盯它。2.1 Claude Code 的输出节奏与日志落点Claude Code 在终端里的输出是流式的它会实时打印自己的思考过程、工具调用读文件、写文件、执行命令和结果。关键的一点是它的输出默认走标准输出stdout这意味着你可以用管道或者重定向把它接到别的地方去。但这里有个坑如果你直接把 stdout 重定向到文件终端里就看不到输出了交互式确认也没法做。所以更合理的做法是用tee命令同时输出到终端和文件claude 重构 src/utils 目录下的工具函数 21 | tee ~/claude-run-$(date %Y%m%d-%H%M%S).log这样终端里正常交互同时所有输出都落到了日志文件里。后续你要做进度监控就是对这个日志文件做文章。Claude Code 还有一个特性值得注意它在执行工具调用时会有比较明确的分隔标记比如读取文件时会打印文件路径执行命令时会打印命令内容。这些标记就是你做进度判断的锚点。2.2 Codex 的执行特征与差异点Codex 的输出风格跟 Claude Code 有区别。它更倾向于把执行过程分成明确的阶段每个阶段有相对清晰的开始和结束标记。但它的日志默认不一定全部走 stdout部分状态信息可能只在终端渲染层展示。实测下来Codex 用同样的tee方式也能捕获大部分关键输出但你可能需要额外关注它的退出码和信号。因为 Codex 在某些错误场景下会直接退出进程而不是打印错误后继续等待。这意味着你的监控方案必须能感知到进程没了这个事件而不只是日志里出现了错误关键词。2.3 两者共同的监控锚点不管用哪个工具以下几类信息是你做远程监控时最需要关注的监控锚点具体表现为什么重要工具调用开始出现文件路径或命令内容知道它当前在操作什么工具调用结束出现结果输出或状态标记判断单步是否完成错误信息包含 error、failed、exception 等第一时间发现异常循环重复相同文件路径或命令反复出现识别卡死或无效循环进程退出进程结束、退出码变化知道任务整体结束把这五类锚点搞清楚后面的监控方案就是围绕怎么及时捕获这些锚点来设计的。3. 用日志轮询加系统通知搭一套轻量监控这是我最推荐的起步方案因为它不依赖任何外部服务纯本地就能跑而且适配 Claude Code 和 Codex 两种工具。核心思路是一个后台脚本定时检查日志文件的变化发现关键锚点就触发系统通知。3.1 日志监控脚本的完整实现先写一个 Python 脚本它的职责是持续 tail 日志文件匹配关键模式然后调用系统通知。为什么用 Python 而不是 shell因为要做状态管理和去重shell 写起来太痛苦。import time import re import subprocess import os from pathlib import Path LOG_FILE Path.home() / claude-run.log CHECK_INTERVAL 5 # 秒 last_size 0 notified_patterns set() # 关键模式定义 PATTERNS { error: re.compile(r(error|failed|exception|traceback), re.IGNORECASE), file_write: re.compile(r(writing|created|modified)\s(\S\.\w), re.IGNORECASE), command_run: re.compile(r(running|executing)\s(.), re.IGNORECASE), loop_warning: re.compile(r(\S\.\w).*\1, re.IGNORECASE), } def send_notification(title, message): 跨平台系统通知 try: if os.uname().sysname Darwin: # macOS subprocess.run([ osascript, -e, fdisplay notification {message} with title {title} ], checkFalse) else: # Linux subprocess.run([notify-send, title, message], checkFalse) except Exception as e: print(f通知发送失败: {e}) def monitor(): global last_size if not LOG_FILE.exists(): print(f等待日志文件出现: {LOG_FILE}) return current_size LOG_FILE.stat().st_size if current_size last_size: return with open(LOG_FILE, r, errorsignore) as f: f.seek(last_size) new_content f.read() last_size current_size for line in new_content.splitlines(): for name, pattern in PATTERNS.items(): match pattern.search(line) if match: key f{name}:{line[:80]} if key not in notified_patterns: notified_patterns.add(key) if name error: send_notification(AI助手异常, line[:100]) elif name loop_warning: send_notification(疑似循环, line[:100]) if __name__ __main__: print(监控启动...) while True: monitor() time.sleep(CHECK_INTERVAL)这个脚本的逻辑很直白每 5 秒检查一次日志文件有没有新增内容有的话就逐行匹配模式命中就发通知。notified_patterns集合用来去重避免同一行反复触发通知。3.2 为什么选择轮询而不是文件系统事件你可能会问为什么不用watchdog这类文件系统事件库实时性不是更好吗我实际对比过两种方案。文件系统事件确实能做到毫秒级响应但它有两个问题第一在某些文件系统比如网络挂载的目录上事件不可靠第二日志写入往往是高频小块的事件触发太频繁反而增加处理开销。而 5 秒轮询对于知道 AI 跑到哪了这个需求来说延迟完全可接受——你又不是做高频交易。更重要的是轮询方案的状态管理更简单脚本崩溃后重启也不会丢状态因为状态就是文件大小和已通知集合可以持久化。3.3 通知渠道的扩展思路系统通知只是最基础的渠道。如果你想要更强的触达能力可以在这个脚本基础上扩展邮件通知用smtplib在检测到错误时发邮件适合长时间离开电脑的场景即时通讯机器人很多团队协作工具都提供 webhook 接口POST 一个 JSON 就能推消息声音提醒macOS 用afplay播放提示音Linux 用aplay适合在同一个房间但没看屏幕的情况我自己的配置是普通进度变化只记日志不通知错误和疑似循环才触发系统通知加声音。这样既不会被打扰又不会错过关键异常。提示通知内容里一定要带上时间戳和日志行号否则你收到通知后还得去日志里翻半天才能定位到具体位置。4. 把终端搬到手机上基于 SSH 的远程查看方案系统通知解决了知道出没出问题但如果你想知道具体跑到哪一步了还是得能看到终端内容。这时候 SSH 就是最直接的方案。4.1 为什么 SSH 方案比想象中简单很多人一听远程查看终端就觉得复杂其实核心就两步本机开启 SSH 服务手机装一个 SSH 客户端。关键在于你要用对工具——tmux或者screen这类终端复用器是必须的。原因很简单如果你直接 SSH 进去跑 Claude Code一旦网络断开SSH 会话结束Claude Code 进程也会收到 SIGHUP 信号然后挂掉。而用 tmux 的话Claude Code 跑在 tmux 会话里SSH 只是attach到这个会话上断开了也不影响进程运行。# 本机创建 tmux 会话并运行 Claude Code tmux new -s ai-work claude 你的任务描述 21 | tee ~/claude-run.log # 按 CtrlB 然后按 D 脱离会话进程继续运行 # 手机上 SSH 进来后重新连接 tmux attach -t ai-work4.2 手机端 SSH 客户端的选型要点手机上的 SSH 客户端选择核心看三个点是否支持密钥认证、是否支持 tmux 的快捷键、断线重连是否稳定。我试过好几款最后固定用一款支持自定义快捷键映射的因为 tmux 的CtrlB前缀在手机软键盘上很难按必须能重映射。配置的时候有个细节容易被忽略把 SSH 连接的超时时间设长一点。手机网络切换WiFi 转蜂窝的时候连接会短暂中断如果超时太短tmux 会话虽然还在但客户端会频繁重连体验很差。4.3 在手机上高效查看日志的技巧手机屏幕小直接tail -f看日志体验不好。我的做法是# 只看关键行过滤掉冗余输出 tail -f ~/claude-run.log | grep -E (error|failed|writing|running|completed) # 或者用 less 的搜索功能按需查看 less F ~/claude-run.log # 按 CtrlC 暂停跟随然后用 / 搜索关键词less F这个用法特别适合手机场景它默认跟随文件末尾类似 tail -f但你随时可以按CtrlC暂停跟随然后像普通 less 一样上下翻页、搜索。看完再按ShiftF恢复跟随。这个交互模式在手机上比纯 tail 好用太多。5. 用状态文件做一层抽象让监控更可靠前面两种方案各有用处但都有一个共同问题它们直接依赖日志文本的格式。一旦 Claude Code 或 Codex 更新了输出格式你的正则匹配可能就失效了。更稳妥的做法是在日志和监控之间加一层状态文件抽象。5.1 状态文件应该记录什么我的做法是写一个轻量的包装脚本它负责启动 AI 助手、解析输出、维护一个 JSON 状态文件。监控端只读这个 JSON不直接碰日志。import json import subprocess import re import time from pathlib import Path from datetime import datetime STATE_FILE Path.home() / .ai-assistant-state.json def update_state(**kwargs): state {} if STATE_FILE.exists(): state json.loads(STATE_FILE.read_text()) state.update(kwargs) state[updated_at] datetime.now().isoformat() STATE_FILE.write_text(json.dumps(state, ensure_asciiFalse, indent2)) def run_with_tracking(task_description): update_state(statusrunning, tasktask_description, started_atdatetime.now().isoformat(), current_step初始化, files_touched[], errors[]) proc subprocess.Popen( [claude, task_description], stdoutsubprocess.PIPE, stderrsubprocess.STDOUT, textTrue, bufsize1 ) files_touched [] errors [] step_count 0 for line in proc.stdout: print(line, end) # 保持终端可见 # 解析文件操作 file_match re.search(r(writing|created|modified)\s(\S\.\w), line, re.I) if file_match: fpath file_match.group(2) if fpath not in files_touched: files_touched.append(fpath) step_count 1 update_state(current_stepf操作文件: {fpath}, files_touchedfiles_touched, step_countstep_count) # 解析错误 if re.search(r(error|failed|exception), line, re.I): errors.append(line.strip()[:200]) update_state(errorserrors[-10:]) # 只保留最近10条 proc.wait() update_state(statusfinished if proc.returncode 0 else failed, exit_codeproc.returncode, finished_atdatetime.now().isoformat()) if __name__ __main__: import sys run_with_tracking( .join(sys.argv[1:]))5.2 状态文件带来的三个好处第一监控逻辑和日志格式解耦。日志格式变了你只需要改解析部分监控端不用动。第二状态可以持久化。脚本重启后读一下 JSON 就知道上次跑到哪了。第三多端消费同一份状态。手机上看、电脑上看、甚至做个网页看都是读同一个 JSON。状态文件的内容大概长这样{ status: running, task: 重构 src/utils 目录下的工具函数, started_at: 2025-01-15T14:23:01, current_step: 操作文件: src/utils/format.js, files_touched: [src/utils/format.js, src/utils/validate.js], step_count: 7, errors: [], updated_at: 2025-01-15T14:25:33 }5.3 基于状态文件的告警规则有了结构化状态告警规则可以写得更精准。比如status running且updated_at超过 3 分钟没变 → 可能卡住了errors数组长度在短时间内快速增长 → 连续出错step_count超过预期值但files_touched没增加 → 可能在无效循环status failed→ 任务失败立即通知这些规则比单纯匹配日志关键词可靠得多因为它们基于的是语义状态而不是文本模式。6. 几个我踩过的坑和对应的解法方案讲完了下面说几个实际用下来踩过的坑。这些在官方文档里基本不会提但你不注意就会浪费很多时间。6.1 日志缓冲导致通知延迟最开始我用tee重定向日志发现通知总是延迟很久才来。排查后发现是 Python 的 stdout 缓冲问题——当输出不是写到终端而是写到管道或文件时Python 默认使用块缓冲而不是行缓冲导致日志内容攒了一大块才写出去。解法是设置环境变量PYTHONUNBUFFERED1或者在命令里加-u参数PYTHONUNBUFFERED1 claude 任务描述 21 | tee ~/claude-run.log这个坑很隐蔽因为你在终端里看输出是实时的终端是行缓冲但日志文件里的内容却是延迟写入的。6.2 正则匹配过于贪婪导致误报我一开始写的错误匹配正则是rerror结果发现日志里只要出现 error handling 这种正常描述也会触发通知。后来改成更精确的模式并且加了白名单过滤ERROR_PATTERN re.compile(r\b(error|failed|exception|traceback)\b, re.I) WHITELIST re.compile(r(error handling|error message format|no error), re.I) def is_real_error(line): if WHITELIST.search(line): return False return bool(ERROR_PATTERN.search(line))经验就是宁可漏报也不要误报。误报多了你会对通知麻木真正的问题反而被忽略。6.3 手机 SSH 断线后 tmux 会话状态混乱有次在手机上 SSH 看 tmux 会话网络切换后重连发现终端显示错乱光标位置不对。这是因为 SSH 断线时终端尺寸信息丢失重连后 tmux 不知道新的窗口大小。解法是在 SSH 客户端配置里加上TERMxterm-256color并且重连后手动执行一次tmux resize-window或者直接CtrlB然后按D再attach。更彻底的做法是在.tmux.conf里加一行set -g aggressive-resize on这样 tmux 会自动跟随最小的客户端尺寸调整。6.4 长时间运行导致日志文件过大Claude Code 跑复杂任务时日志文件可能涨到几十兆。这会导致两个问题监控脚本读取变慢手机端查看卡顿。解法是加日志轮转。最简单的做法是在包装脚本里判断文件大小超过阈值就切分def rotate_log_if_needed(log_path, max_size_mb10): if log_path.exists() and log_path.stat().st_size max_size_mb * 1024 * 1024: backup log_path.with_suffix(f.{int(time.time())}.log) log_path.rename(backup) return True return False或者直接用系统的logrotate配置更省心。7. 不同场景下的方案组合建议最后说一下不同使用场景下我会怎么组合这些方案。没有万能方案关键是匹配你的实际需求。场景推荐组合理由在家但不在电脑前系统通知 状态文件延迟要求不高本地通知够用通勤路上想查看SSH tmux 状态文件需要看到具体内容SSH 最直接长时间跑大任务状态文件 邮件/IM通知需要可靠触达不依赖本地网络多人协作查看状态文件 共享看板多人消费同一份状态需要统一入口我自己的日常配置是所有任务都通过包装脚本启动状态文件常驻系统通知只开错误和循环告警手机 SSH 作为深度查看的备用手段。这套组合用了几个月基本没再出现过不知道 AI 跑到哪了的焦虑。有一点需要提醒不要过度监控。我见过有人把通知配得特别细每改一个文件就推一条消息结果手机响个不停最后直接把通知关了。监控的目的是减少焦虑不是制造新的焦虑。找到那个刚好够用的粒度比追求全面覆盖更重要。另外如果你用的是团队协作环境状态文件可以放在共享目录里这样同事也能看到任务进度。但要注意日志里可能包含代码内容共享前确认一下有没有敏感信息。这个边界自己把握好就行。