无人值守自动任务加固经验库(Unattended Task Pitfalls)
来源:一个 AI agent 工作区的真实事故记录(2026-08 两次备份损坏事故 + cron 总结误判事故复盘)。适用任何带定时任务的 agent 环境。
核心命题:无人值守链路里,操作纪律会失效,只有代码能兜底。
§0 使用说明
- 搭建新的定时/自动化任务前 → 通读 §1-§3(防事故)
- 定时任务出现"结果不对但显示成功" → §3(验证链)+ §4(隔离会话)
- 排查"命令报错但其实执行了" → §2.3
- 设计任务的通知行为 → §5
§1 核心哲学:记忆型教训在无人值守场景必然失效
真实事故:08-27 备份产出损坏文件上传云端,教训"gzip -t 验证后才能上传"被记入 agent 长期记忆。08-30 定时任务再次产出 528MB 截断坏文件(正常 911MB)并上传——因为 cron 会话读不到"操作纪律",纪律只对在线的 agent 有效。
结论:
- 凡是"以后要注意 XX"类教训,先问:能不能写成代码? 能写进脚本的一律写进脚本
- 记忆/文档只作人侧兜底,不是无人值守链路的防线
- 判断一个防护是否有效:假设 agent 完全不在线,脚本独自运行,这个防护还在不在?
§2 执行层:长任务怎么跑
§2.1 后台化(防进程被杀)
- 长任务(打包/上传/批量处理)禁止在前台直接跑:平台网关常有 2-10 分钟超时,超时杀进程 → 留下半成品文件
- 标准模式:
setsid nohup <cmd> > /tmp/task.log 2>&1 &,然后轮询日志/文件时间戳确认进度 - 佐证数据:同一个备份脚本,前台跑两次两次被杀,后台跑零事故
§2.2 半成品的危害大于失败
进程被杀不是"任务失败",而是"任务产出了一个损坏的产物"。若下游逻辑(上传/清理/状态标记)不感知损坏,坏产物会流转进正式链路(本例:坏备份被上传到云端"永久保留"区)。
§2.3 网关 504 ≠ 命令失败
- 工具调用层报 504 Gateway Timeout,底层命令可能已经执行成功(尤其 setsid 后台任务不受会话死亡影响)
- 正确处置:先
ps查进程、查产物文件时间戳,确认实际状态再决定是否重发 - 危险反面案例:盲目重发副作用命令(删除/上传/发布)→ 重复执行事故
- 504 后重发的三个前置检查:进程在不在 / 产物变没变 / 任务是否幂等
§3 产物验证链(核心章节)
以"打包 → 上传 → 标记成功"类任务为例,完整验证链五环节:
§3.1 打包后本地验证
gzip -t <archive> # 压缩流完整性(截断文件在此暴露)
tar -tzf <archive> > /dev/null # 归档结构抽查(防 tar 头损坏)
两者都过才算产物有效。gzip -t 约 8 秒 / 900MB,成本可忽略。
§3.2 验证不过 → 不上传 + 不更新状态
- 上传前验证失败:跳过上传,保持上一份好的远端文件不动
- 状态文件(last_run 时间戳)只在"上传成功"后写入——失败则下轮自动重试,不会因状态已标记而跳过
§3.3 "已存在则跳过"短路必须先验证
增量/幂等脚本常见 if 存在今日产物: return。坑:产物存在 ≠ 产物完好。正确写法:
if exists(today_file):
if verify(today_file): return today_file # 真完成,跳过
else: os.remove(today_file) # 损坏,删除重打包(自愈)
真实踩坑:重跑修复任务时被短路逻辑复用坏文件,幸好上传前验证兜住。
§3.4 远端核对(不下载大文件)
WebDAV 用 PROPFIND + 正则取 getcontentlength,与本地字节数比对即可确认远端文件是否同坏。同类协议找等价的元数据接口,禁止为了验证下载 500MB。
§3.5 参考实现
见 scripts/verify_archive.py——打包验证 + 已存在自愈 + 上传成功才写状态的最小可复用模板。
§4 隔离会话:cron 看不到你以为它看得到的东西
真实事故:每日总结 cron 连续两天误判"全天无对话,静默结束"——实际当天有多轮主会话交互。根因:cron 会话与主会话隔离,对话历史、记忆快照可能都不在上下文里(且部分平台对话存储对 cron 完全不可见)。
§4.1 文件系统证据法
cron 里下"无活动"结论前,强制扫一遍文件系统:
find <workspace> -newermt "今日00:00" ! -newermt "明日00:00" -type f
有当日修改的文件 → 有活动,去读文件内容还原事实。
§4.2 排除自动产物
每次会话启动都会刷新时间戳的文件(.env、自动生成的 README、缓存)不算人类活动证据,先建排除表再判断。
§4.3 时间归属约定
与用户约定的时间归属规则(如"凌晨 0-2 点归前一天")要在归档类任务里显式实现,否则跨日任务的时间戳归属会跟主会话记录打架。
§5 通知设计
§5.1 两类机制的分工
| 机制 | 语义 | 适用 |
|---|---|---|
| 定时任务(cron) | 无条件通知:每次运行必发结果 | 结果本身有价值(备份/日报) |
| 心跳(heartbeat) | 条件通知:有情况才发 | 多检查项聚合、需要会话上下文 |
§5.2 通知格式纪律
- 结果 + 关键指标 + 异常标注(✓/✗),失败必附原因和日志位置
- 自动任务输出禁用拟人内容("早点睡哦"类关怀语)——任务运行时用户根本不在线,出现只会诡异
- 高频无变化任务设静默档(如 skill 更新扫描:无更新不上报),避免通知疲劳
§6 交叉验证与任务生命周期
§6.1 自动任务的产物也要被自动检查
单一任务的自我报告不可信(本例:备份任务报"上传成功",实际传的是坏文件)。模式:另一个任务例行抽查产物(每日总结任务顺手 gzip -t 备份——这是发现 08-30 事故的唯一渠道)。至少给关键产物配一条独立验证路径。
§6.2 一次性任务自删
定时复查类任务(如"45分钟后确认发布状态")完成后应自删,避免次日重复执行。一次性触发用 at 型调度,勿用 every 型。
§6.3 失败的默认出口
无人值守任务失败的默认行为 = 记录日志 + 通知用户 + 保留现场。不要在失败路径上做自动重试以外的主观决策(删数据/覆盖文件/换路径),这些留给下一次有人在线时处理。
附:事故案例速查
| 日期 | 事故 | 对应章节 |
|---|---|---|
| 08-27 | 备份前台跑被网关杀,坏文件上传云端 | §2.1 §2.2 §3.1 |
| 08-30 | 同款事故复发(教训只在记忆未进代码) | §1 |
| 08-30 | 重跑被"已存在跳过"复用坏文件 | §3.3 |
| 08-30 | 网关 504 但命令实际已执行 | §2.3 |
| 08-24/25 | cron 总结误判"全天无对话" | §4 |
更新日志
- v1.0.0(2026-08-31):首版。两次备份事故 + cron 误判事故的完整复盘沉淀
