升级成功,不等于服务恢复:一次 OpenClaw 9.1 升级复盘
URL
date
Sep 5, 2026
slug
openclaw-91-safe-upgrade
summary
一次多 Agent 服务升级:先验证回滚,再恢复能力,最后用真实证据而不是 Health OK 结案。
status
Published
tags
OpenClaw
运维
AI Agent
复盘
type
Post
升级成功,不等于服务恢复:一次 OpenClaw 9.1 升级复盘
2026 年 9 月 5 日

最难回答的运维问题,有时不是“怎么升级”,而是用户问的那句:“为什么还没完成,你打算什么时候完成?”
这次把一套多 Agent 的 OpenClaw 从 2026.8.1 升到 2026.9.1,我们备份了数据、请另一个模型审查了方案、安装了新版,还看到了
Health OK。但在一个阶段,Telegram、飞书和定时任务仍被留在临时关闭状态。安装完成了,用户需要的服务却还没回来。这篇文章记录真实的检查结果,也记录判断错误。它不是一份“照抄几条命令就能安全升级”的保证书,而是一份关于变更边界、可回退性和验收证据的复盘。
过程简图:预检与独立审查 → 停止接入并冷备 → 安装与插件对齐 → 隔离验收 → 操作会话中断造成恢复延迟 → 恢复渠道 → 恢复调度与复核。
几个术语先说明:Gateway 是接收聊天并协调 Agent 的服务;Doctor 是配置检查与迁移工具;WAL 是 SQLite 的预写日志;offset 是消息消费进度;scratch 是存放在数据库里的任务备注。
本文的脱敏目标是去除凭据、个人身份、私人对话和网络标识,而非隐瞒所用的公开软件。保留公开版本号用于机制核对;日期为文章发布日期,不提供精确事故时间戳。
一、先定义成功:用户能继续做事,而不是版本号变了
这套系统有多个 Agent,接入不同聊天渠道,并管理独立的定时任务。Agent 需要宿主机 shell、文件读写、工具调用和本地记忆检索。
升级目标必须保留这些能力。不能为了避免进程被误停,就把全部 Agent 塞进容器、收回宿主机权限;那是在用另一种功能损坏替代原来的风险。
我们把成功拆成四层:
- 程序层: 核心版本正确,插件可用,服务能启动。
- 能力层: 原来的模型路由、宿主机工具、记忆检索没有丢。
- 数据层: 会话和消息记录保留,数据库完整,任务定义没有意外变化。
- 用户层: 聊天接入与定时调度恢复,真实请求可以完成,而不是只剩一个活着的端口。
Health OK 只是一条证据,不是这四层的总和。二、先审查计划,而不是先停机
我们把升级与回滚计划交给另一个模型独立审查。第一轮结果是 NO-GO:计划方向正确,但关键前置条件不够具体。
其中最有价值的质疑有四个:
- 只预下载核心包,没有确认所有独立插件的新版本都能取得。
- 数据库能恢复,不代表恢复后的消息队列不会重复投递。
- 升级任务不能依附在即将被停止的 Gateway 进程树里。
- “插件数量没少”不能替代“每个 Agent 实际拥有的工具没少”。
修订后,第二轮得到有条件的 GO。条件包括核对安装产物、验证隔离接入、限制临时配置差异,以及按顺序恢复渠道和调度。
这里也要避免另一个迷信:审查意见是待验证的风险清单,不是事实来源。 比如审查提到本地模型插件可能需要原生编译,我们检查候选包后发现,这个版本的相关插件没有声明需要那种构建的依赖;本机使用的是独立管理的模型服务。该查的是实际包,不是机械执行审稿人的猜测。
三、回滚不是“再安装一次旧版本”

这次保留了完整的冷备份,包括旧核心及依赖、独立插件、配置与凭据、运行状态、模型文件和各 Agent 工作区。备份目录不进入公开发布渠道。
关键不是体积,而是匹配关系。本次采用文件级冷备:先停止服务,确认监听关闭、运行状态文件没有可见写入者,再复制数据库及其现存的 WAL,并保留现场辅助文件;在备份前后复查状态,并比较源与快照。它不是对正在写入的数据库随手复制。SQLite 的
-shm 是可重建的临时索引,不是恢复必需的数据;保留它用于现场归档不意味着恢复必须覆盖它。实际未把 checkpoint 成功当成本次已完成的步骤,采用的是静止主库与对应 WAL 的文件快照。进程和文件检查有观察边界,不能据此保证其他操作者永远不会进入;无法可靠停写时,应使用 SQLite Backup API 等一致性备份方式。- 旧程序与旧插件要成套保留。 离线恢复不能假设 npm 或网络一直可用。
- 数据库与 WAL 要成套保留。 如果仍有未合并写入,只拷贝主数据库文件可能拿到不完整状态。
- 新程序迁移过的数据不能直接交给旧程序。 数据格式兼容性必须验证,不能赌。
- 备份本体不拿来“移动恢复”。 失败现场先归档,再从备份复制回去,保留再次恢复的机会。
- 业务工作区不盲目覆盖。 升级期间新增的笔记或用户文件不能因为回滚而消失。
我们使用文件摘要比较源与快照,并检查全局状态库及各个 Agent 数据库的完整性。回滚脚本先在隔离测试目录里演练:旧状态恢复、新版本证据保留、业务文件不被覆盖。
但这只是测试数据演练加生产快照校验,不是已经在生产上完成过一次完整回滚。两者必须说清。
真正危险的分界线:新版是否接收过业务
隔离验收阶段,如果新版还没接收外部消息、没运行业务调度,恢复冷快照相对容易界定。
一旦新版本处理了消息或发出了报告,恢复旧快照就可能让任务状态倒退。除了保存新消息,必须考虑已经发送过的内容会不会再发一次。
因此我们明确划线:恢复生产接入之后,不再自动、盲目执行冷回滚。 必须先排空、保留现场、核对消息与任务回执,再决定恢复和重放策略。不要把“数据库可恢复”包装成“业务天然无损”。
四、安装器说成功,独立插件却仍停在旧版
核心更新到 2026.9.1 后,安装器返回成功。但进一步检查发现:多个独立官方插件仍维持在固定的 2026.8.1。
这不是依靠多等几分钟能解决的问题。我们逐个把这些已预检的插件固定到 2026.9.1,再验证它们实际加载的版本与依赖状态。
安装产物还与预先校验过的发布包进行了逐文件比对。唯一需要解释的差异是生命周期“待完成”标记:官方安装完成逻辑会删除这个标记,不能机械地把它当成损坏文件,也不能不看源码就随便忽略。
配置也发生了迁移:旧的执行权限字段组合被转换为新的
mode: full。正确验收方法不是要求 JSON 逐字不变,而是区分:- 官方 schema 的等价迁移;
- 安装器新增但原本不需要的功能;
- 用户已有配置被意外改变。
我们用宿主机读写实测确认执行能力仍然存在,同时没有更换原来的 Node 运行时和服务启动参数。
五、隔离验收有价值,但不能把用户一直留在维护窗口
首次启动新版时,我们暂时关闭聊天渠道接入和 Cron 调度,让 Gateway 先进入验收状态。检查覆盖了渠道未启动、调度禁用、Telegram offset 未推进和数据库状态。
随后,所有 Agent 分别在隔离会话里执行宿主机检查,并在自己的工作区创建、读取、删除专用测试文件。核对实际工具回执,确认不是模型口头说“我能访问”。
还逐一比较了升级前后的模型可见工具清单,所有 Agent 都没有减项。本地记忆既做了 CLI 搜索,也做了 Agent 的
memory_search 实际调用。这次最该认错的地方
验收过程中,操作会话触及工具调用上限。后续收到后台通知时,我们反复解释结果,却没有及时推进恢复,导致维护窗口不必要地延长。
直接原因是操作会话在隔离验收阶段中断;促成因素是没有在会话结束前完成接管,且把解释后台通知当成了任务推进。用户影响是维护窗口被不必要地延长。恢复动作是重新确认隔离边界、补做验收、依次开放渠道和调度。后续应由变更执行者在计划中写明接管人或接管会话,交接验收标准是“下一位操作者无需猜测当前接入与调度状态即可继续”,而非只留一份版本安装日志。
这不是“为了安全所以只能等”的合理借口。安全方案必须包括有时间预算的收尾和明确交接:
- 哪些验收是恢复前的硬门槛,哪些可以恢复后观察。
- 操作者中断后,谁接管剩余步骤,当前服务究竟在哪个阶段。
- 超过预算时,是恢复旧版、继续受控验收,还是请求用户决定。
- 业务已激活后,绝不能由一个不了解新消息状态的自动回滚脚本接管。
这些是本次暴露出来、今后需要强化的流程要求,不冒充已经实现的自动恢复系统。
用户真正需要的是服务回来,而不是不断收到“后台任务结束了”的解释。
六、验收结果:哪些已证实,哪些没有
本次恢复完成后的已核验结果:
- 核心和多个独立官方插件安装为 2026.9.1,原有必需插件可用。
- 所有已配置的 Telegram 账号和飞书恢复连接,全部临时维护开关撤销。
- 所有 Agent 的宿主机读写、工具回执及工具清单比对通过。
- 全部关键数据库完整性通过;验收时原有会话键和消息记录无缺失、无改写。
- 全部 Cron 定义及启停状态保留;已观察到调度恢复运行,但不据此推断整个维护窗口绝无漏跑或重复发送。
- 本地记忆索引干净,已存来源和片段均可检索;搜索返回结果,Agent 工具调用成功。
- 监控任务既有手动验证,也有自然调度成功记录,不能把两者混为一谈。
验收不是一句“都绿了”
- 安装产物:按 HTTPS npm registry 的
dist.integrity校验 tarball 的 SHA-512,再比较包内文件和实际安装文件的 SHA-256;安装器按源码移除的生命周期标记单独解释。此校验依赖 registry 的可信性,只验证拿到的包与 registry 声明一致,不声称独立证明供应链安全。
- 数据完整性:逐库运行
integrity_check;这不能证明投递不会重复。
- 消息保留:按原有
(session_id, seq)对照记录,检查缺行及event_json内容变化,并核对原有会话键;不是只比较总行数。
- 任务定义:比较 Cron ID 和去除动态运行状态后的定义,要求目的地、内容、启停配置未变。
- 隔离边界:对照数据库中 Telegram offset 记录,检查冷快照与激活前终点一致;结合渠道状态检查。网络监测只覆盖运行期间的采样,不能证明每个瞬间都没有连接。
- 调度恢复:分别记录手动监控测试与自然执行回执。没有覆盖所有任务实例和外发副作用,因此不承诺整个窗口零漏跑、零重复。
真实用户请求也在恢复后进入系统,完成了图片理解、工具使用和最终回答,并有投递镜像记录。不过,没有逐一取得所有账号真实回复的平台消息 ID,因此不能写成“所有账号的完整端到端聊天都逐个验过”。
边界同样重要:短时进程稳定不证明长期无故障;外部脚本检查中的部分项目只覆盖语法,没有执行实际业务或验证其外部接口;仍有升级前的业务任务失败记录,不因为服务升级就自动成为“已修好”。心跳空模板则是预期跳过,不是这类失败。
本次结案的含义是:升级、核心能力和生产服务恢复通过验收;不是所有业务自动化永远不会再失败。
七、一个误报教训:没有 HEARTBEAT.md,不等于心跳坏了

恢复后,所有 Agent 的心跳任务出现:
heartbeat skipped: empty-heartbeat-file这是恢复后排查告警时发现的次生误判,不是升级停机的原因。最初我们看到工作区里没有
HEARTBEAT.md,就把它解释成“主动巡检缺了一块”。这一步判断是错误的。检查 2026.9.1 的运行时代码和随包文档后,事实是:
- 心跳指令从数据库中的任务 scratch 读取。
- 运行时不再依赖
HEARTBEAT.md;Doctor 提供把旧文件迁入 scratch 的迁移路径。
- 如果 scratch 存在,但只有注释、标题等有效空内容,会跳过周期性模型调用。
empty-heartbeat-file是沿用旧命名的跳过原因,不能按字面诊断。
再检查数据库,所有 Agent 的 scratch 都存在,内容全是默认空模板。模板明确说明:保持空白或仅有注释,就跳过心跳 API 调用;有实际任务时再添加。
所以本次不是漏做了已配置的巡检,而是根本没有为这些默认心跳安排具体工作。 没必要创建一批空文件,更不该编造待办让它们每半小时消耗模型调用。
还有一个容易忽略的细节:文档区分“scratch 有效为空”和“scratch 不存在”。后者不应被自动等同于前者。这也说明,修复前必须核查真实状态源,而不是只搜索一个文件名。
八、留给下一次升级的检查单
- 固定目标版本,先读发布说明和 CLI 帮助,再核实当前安装形态。
- 把插件、运行时、配置语义、数据格式和外部脚本都纳入变更范围。