OpenClaw 2026.9.9 升级故障复盘

背景:本文记录一次基于 1Panel、Docker 部署的 OpenClaw 升级故障。

内容包含:升级后先后遇到飞书授权状态迁移失败、配置 Schema 不兼容、openclaw-lark 插件无法加载,以及 SQLite ownership 丢失。最终通过状态归属修正、配置字段迁移、插件兼容补丁、本地重新打包和状态锁恢复完成修复。

一、环境与最终结果

1. 环境

  • 部署方式:1Panel + Docker
  • OpenClaw:2026.9.9
  • 飞书插件原版本:@larksuite/openclaw-lark@2026.7.9
  • 飞书插件升级目标:@larksuite/openclaw-lark@2026.7.16 (明显这个版本是旧版本了)
  • 飞书接入方式:WebSocket 长连接
  • 飞书账号:main-bot、media-bot
  • 其他频道:Telegram

2. 最终状态

修复后的插件检查结果:

1
2
3
4
5
version: 2026.7.16-oc9.9.1
status: loaded
error: None
tools: 39
channels: ['feishu']

容器状态:

1
2
3
status=running
running=true
restartCount=0

最终使用本地修改后的补丁包:

1
larksuite-openclaw-lark-2026.7.16-oc9.9.1.tgz

二、故障并不是单点问题

这次升级同时暴露了四类兼容问题:

  1. 旧飞书授权状态无法确定所属账号;
  2. 旧飞书配置字段不符合新版严格 Schema;
  3. openclaw-lark 插件与新版 Plugin SDK、模块格式和 Runtime API 不兼容;
  4. 修复期间 one-off 容器跨 PID namespace 访问 SQLite,导致 Gateway 丢失 ownership。

其中,第三项是工作量最大的问题;

第四项则是修复过程中额外触发的 OpenClaw 2026.9.x 状态锁问题。


三、第一阶段:旧飞书授权状态迁移失败

1. 故障日志

升级后 Doctor 首先报错:

1
2
3
4
5
6
Legacy channel allowFrom channel/account is unresolved;
left in place at /home/node/.openclaw/credentials/feishu-main-allowFrom.json

Doctor stopped because a state migration refused to continue.

Failing check channel-pairing (step-refused)

后续迁移全部被阻塞:

1
2
3
4
plugin-doctor-state (blocked-by-prior-refusal)
sessions (blocked-by-prior-refusal)
acp-session-metadata (blocked-by-prior-refusal)
agent-dir (blocked-by-prior-refusal)

2. 根因

旧授权文件名为:

1
feishu-main-allowFrom.json

这表示旧状态属于飞书账号 main。

但新版配置中的实际账号是:

1
2
3
main-bot
media-bot
default

配置中不存在 main。

新版 OpenClaw 会把旧 JSON 授权名单迁移到 SQLite,并要求每条记录都能明确归属到:

1
channel + account

由于迁移程序无法判断旧 main 应该对应 main-bot 还是其他账号,为避免把授权记录迁移到错误账号,它主动拒绝继续。

3. 修复方式

首先明确默认账号:

1
2
3
4
5
6
7
{
  "channels": {
    "feishu": {
      "defaultAccount": "main-bot"
    }
  }
}

然后将旧授权文件关联到实际账号:

1
2
3
mv \
  "$OPENCLAW_DATA/credentials/feishu-main-allowFrom.json" \
  "$OPENCLAW_DATA/credentials/feishu-main-bot-allowFrom.json"

同时清理没有 appId/appSecret 的遗留 accounts.default,避免它被继续识别成一个真实飞书账号。

修复后,Doctor 能继续执行 Provider catalog、Session SQLite、Agent DB 等后续迁移。


四、第二阶段:飞书配置 Schema 不兼容

1. 故障日志

插件安装或配置读取时报错:

1
2
3
4
OpenClaw config is invalid

channels.feishu: invalid config for plugin feishu:
must not have additional properties: "replyMode", "threadSession"

2. 根因

旧配置包含:

1
2
3
4
{
  "replyMode": "auto",
  "threadSession": true
}

新版飞书配置 Schema 使用严格校验:

1
additionalProperties: false

只要配置中出现 Schema 未声明的字段,整个 openclaw.json 就会被拒绝。

3. 新旧字段映射

旧配置 新配置
replyMode: "auto" 删除,由 streaming 等配置控制
threadSession: true groupSessionScope: "group_topic"
线程内回复 replyInThread: "enabled"

4. 修复后的飞书配置

以下示例已对凭据和群 ID 做变量化处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
  "channels": {
    "feishu": {
      "enabled": true,
      "defaultAccount": "main-bot",
      "groupPolicy": "allowlist",
      "groupAllowFrom": [
        "${FEISHU_MAIN_GID}",
        "${FEISHU_CODING_GID}",
        "${FEISHU_MEDIA_GID}"
      ],
      "accounts": {
        "main-bot": {
          "appId": "${FEISHU_APP_ID}",
          "appSecret": "${FEISHU_APP_SECRET}",
          "dmPolicy": "pairing",
          "enabled": true,
          "name": "main-bot"
        },
        "media-bot": {
          "appId": "${FEISHU_MEDIA_BOT_ID}",
          "appSecret": "${FEISHU_MEDIA_BOT_SECRET}",
          "dmPolicy": "pairing",
          "enabled": true,
          "name": "media-bot"
        }
      },
      "replyInThread": "enabled",
      "groupSessionScope": "group_topic",
      "requireMention": false,
      "streaming": true,
      "domain": "feishu",
      "connectionMode": "websocket",
      "webhookPath": "/feishu/events",
      "dmPolicy": "pairing",
      "reactionNotifications": "own",
      "typingIndicator": true,
      "resolveSenderNames": true
    }
  }
}

其中:

  • groupAllowFrom 中使用 oc_... 格式的群聊 ID;
  • groupSessionScope: "group_topic" 表示不同飞书话题使用独立会话;
  • replyInThread: "enabled" 表示机器人在话题线程中回复;
  • requireMention: false 表示白名单群中无需 @机器人 也可以触发。

五、第三阶段:openclaw-lark 与 OpenClaw 2026.9.9 不兼容

这是本次故障的核心部分。

1. 原始错误

1
2
3
4
5
[plugins] openclaw-lark failed during load

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]:
Package subpath './plugin-sdk' is not defined by "exports"
in openclaw/package.json

插件原版本:

1
@larksuite/openclaw-lark@2026.7.9

升级到当时的稳定版本:

1
@larksuite/openclaw-lark@2026.7.16

但 2026.7.16 仍然没有适配 OpenClaw 2026.9.9。


2. Plugin SDK 路径已经变化

插件运行时代码仍然引用:

1
2
require("openclaw/plugin-sdk")
require("openclaw/plugin-sdk/channel-runtime")

检查 OpenClaw 2026.9.9 的 exports 后确认:

1
2
3
4
5
./plugin-sdk                         NOT EXPORTED
./plugin-sdk/core                    EXPORTED
./plugin-sdk/channel-runtime         NOT EXPORTED
./plugin-sdk/channel-outbound        EXPORTED
./plugin-sdk/channel-message         EXPORTED

同时验证插件使用的函数:

1
2
3
emptyPluginConfigSchema
createReplyPrefixContext
createTypingCallbacks

对应的新模块均能正确导出这些函数。

修复

在插件入口中:

1
require("openclaw/plugin-sdk")

改为:

1
require("openclaw/plugin-sdk/core")

在回复分发器中:

1
require("openclaw/plugin-sdk/channel-runtime")

改为:

1
require("openclaw/plugin-sdk/channel-outbound")

实际修改文件:

1
2
index.js
src/card/reply-dispatcher.js

.d.ts 中的 import type 只是 TypeScript 类型声明,不参与运行时加载,无需修改。


3. npm 包存在 CJS/ESM 发布格式错误

修复 SDK 路径后,又出现:

1
ReferenceError: exports is not defined in ES module scope

根因

插件实际发布的 index.js 是 CommonJS:

1
2
3
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const plugin_sdk_1 = require("openclaw/plugin-sdk/core");

但 package.json 却通过 exports.import 指向不存在的 ESM 文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.mts",
        "default": "./dist/index.mjs"
      }
    }
  },
  "main": "./dist/index.js"
}

而 npm 包中真正存在的入口是:

1
./index.js

OpenClaw Loader 因 exports.import 将 CommonJS 入口当成 ESM 执行,于是 CommonJS 的 exports 全局变量不存在。

修复

将插件明确声明为 CommonJS,并让所有入口指向实际文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "type": "commonjs",
  "main": "./index.js",
  "types": "./index.d.ts",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "require": "./index.js",
      "import": "./index.js",
      "default": "./index.js"
    }
  },
  "openclaw": {
    "extensions": [
      "./index.js"
    ]
  }
}

4. CommonJS 文件中残留 import.meta

插件的 CommonJS 编译产物中还存在 ESM 专用语法:

1
import.meta.url

涉及:

1
2
src/core/version.js
src/core/token-store.js

修复

version.js 改用 CommonJS 自带的:

1
__dirname

token-store.js 改用:

1
createRequire(__filename)

完成后直接用 Node 验证入口:

1
2
3
ENTRY_LOAD_OK
exportCount: 42
feishuPlugin: object

这说明插件已经能够作为 CommonJS 被正确加载。


5. Runtime API 已从 loadConfig 迁移为 current

插件中还存在旧调用:

1
runtime.config.loadConfig()

OpenClaw 2026.9.9 对应的新 API 是:

1
runtime.config.current()

涉及两个文件:

1
2
src/channel/monitor.js
src/core/lark-client.js

修复

1
return LarkClient.runtime.config.loadConfig();

改为:

1
return LarkClient.runtime.config.current();

以及:

1
const live = LarkClient.runtime.config.loadConfig();

改为:

1
const live = LarkClient.runtime.config.current();

六、第四阶段:重新打包插件并恢复依赖与来源完整性

1. 为什么不能直接修改 managed package

直接修改 OpenClaw 管理的 npm 插件目录后,插件虽然能够加载,但检查结果会显示:

1
trust.reason: provenance-invalid

原因是:

  • OpenClaw 安装时记录了 npm 包来源和完整性;
  • 手工修改 JS 文件后,实际内容与登记内容不一致;
  • 插件因此被标记为来源完整性异常。

2. 为什么本地目录安装还不够

第一次从本地目录安装后,又出现:

1
Plugin dependency @larksuiteoapi/node-sdk is missing

直接复制本地源码目录没有完整安装 npm dependencies。

3. 最终方案:重新打成 tgz

将修补后的插件版本标记为:

1
2026.7.16-oc9.9.1

然后执行:

1
npm pack

得到:

1
larksuite-openclaw-lark-2026.7.16-oc9.9.1.tgz

通过 OpenClaw 的 npm-pack: 来源覆盖安装:

1
2
3
openclaw plugins install \
  npm-pack:/home/node/.openclaw/local-plugins/dist/larksuite-openclaw-lark-2026.7.16-oc9.9.1.tgz \
  --force

这样 OpenClaw 会:

  1. 解压补丁包;
  2. 安装生产依赖;
  3. 处理 OpenClaw peer dependency;
  4. 运行插件加载检查;
  5. 重新登记插件来源。

最终检查结果:

1
2
3
4
5
6
version: 2026.7.16-oc9.9.1
status: loaded
error: None
trust.reason: origin-path
tools: 39
channels: ['feishu']

origin-path 是本地补丁来源标记,不再是 provenance-invalid。


七、第五阶段:SQLite ownership 丢失

插件完成修复后,Gateway 又出现:

1
2
OpenClaw state ownership at .../openclaw.sqlite could not be verified;
retry after maintenance finishes.

同时 Telegram、Device Pair 和 Delivery Recovery 持续报错:

1
2
3
Shared-state read and cleanup failed
isolated polling spool drain failed
Delivery recovery failed

1. 根因

修复过程中执行了多个 one-off 容器:

1
docker run --rm --volumes-from "$CONTAINER" ...

这些容器与正式 Gateway:

  • 挂载同一个 OpenClaw SQLite;
  • 但处于不同的 Linux PID namespace。

OpenClaw 2026.9.x 的 ownership 判断可能把另一个 namespace 中不可见的 PID 误判为已经死亡,从而回收或覆盖 ownership。

正式 Gateway 随后发现自己不再拥有 SQLite,于是拒绝继续读写。

2. 修复方式

完全停止正式容器:

1
2
docker update --restart=no "$CONTAINER"
docker stop -t 30 "$CONTAINER"

确认没有其他共享状态目录的 OpenClaw 容器,然后等待 ownership heartbeat 保护窗口过期:

1
sleep 100

最后只启动正式容器:

1
docker start "$CONTAINER"

恢复结果:

1
2
3
status=running
running=true
restartCount=0

3. 操作规范

在 OpenClaw 2026.9.9 上:

Gateway 运行期间

不要执行:

1
docker run --rm --volumes-from "$CONTAINER" openclaw ...

应使用同一个容器 namespace:

1
docker exec "$CONTAINER" openclaw ...

Gateway 停止期间

可以使用 one-off 容器,但 one-off 命令退出后,应确认没有残留进程,并给 ownership lease 足够的释放时间,再启动正式 Gateway。

不要手工删除:

1
2
3
4
5
openclaw.sqlite
openclaw.sqlite-wal
openclaw.sqlite-shm
gateway.state.lock
state*.lock

八、Session SQLite 迁移结果

升级期间还完成了大量会话迁移:

1
2
3
4
5
6
Repaired 969 of 969 missing session titles
Legacy entries: 1353
SQLite entries: 1350
Transcript events imported: 62606
Archived legacy transcript artifacts: 4018
Archived unreferenced JSONL artifacts: 7413

剩余 3 条历史异常:

  • 两个 JSONL 的 transcript header 与原文件名不一致;
  • 一个 Session 索引存在,但对应 JSONL 已丢失。

数量关系正好是:

1
1353 - 1350 = 3

这些问题没有阻止核心迁移和 Gateway 启动,因此当时没有直接执行 destructive recovery 或 cleanup。

在确认历史会话完整并完成备份前,不应直接运行:

1
openclaw update cleanup --yes

九、最终修复清单

项目 修复结果
feishu-main-allowFrom.json 无法确定账号 映射到 main-bot
缺少 defaultAccount 设置为 main-bot
无凭据的 accounts.default 清理
replyMode 删除
threadSession 迁移为 groupSessionScope 和 replyInThread
openclaw/plugin-sdk 改为 openclaw/plugin-sdk/core
channel-runtime 改为 channel-outbound
CommonJS/ESM 冲突 修正 package.json
CommonJS 中的 import.meta 改用 __dirname / __filename
runtime.config.loadConfig() 改为 runtime.config.current()
插件生产依赖缺失 使用 npm tgz 安装
provenance-invalid 重新打包并登记本地来源
SQLite ownership 丢失 停止、等待 lease 过期、单独启动 Gateway
最终插件状态 loaded
飞书工具数量 39
容器状态 running,重启次数 0

十、经验总结

1. 不要只看 peer dependency

插件声明:

1
openclaw >= 某版本

并不代表它真实兼容所有更高版本。Plugin SDK、Runtime API 和插件加载器都可能发生 breaking change。

2. 应测试发布后的 npm tarball

源码仓库能构建,不代表 npm 发布包正确。本次发布包同时存在:

  • CommonJS 编译产物;
  • ESM exports;
  • 指向不存在的 dist;
  • CommonJS 文件残留 import.meta;
  • 旧 Plugin SDK 路径。

真正需要测试的是:

1
npm pack

生成的最终 tarball,而不是只测试源码目录。

3. 数据迁移失败时不要直接删除旧文件

allowFrom、Session JSONL 和 SQLite 都属于用户状态。迁移失败时应先确认归属关系并备份,而不是直接删除阻塞文件。

4. Docker PID namespace 会影响状态锁判断

共享同一 SQLite 的容器不等于共享 PID namespace。涉及数据库 ownership 的 CLI 应优先在正式容器中执行,或在 Gateway 完全停止后执行。

5. 补丁必须重新打包

直接修改 managed package 只适合验证,不适合作为最终状态。正式修复应:

  1. 保存补丁源;
  2. 设置可辨识版本;
  3. 重新打 tgz;
  4. 通过插件管理器安装;
  5. 再次验证来源、依赖和 Runtime 状态。

十一、后续建议

  1. 保留本地补丁包:

    1
    
    local-plugins/dist/larksuite-openclaw-lark-2026.7.16-oc9.9.1.tgz
    
  2. 暂时不要直接执行:

    1
    
    openclaw plugins update openclaw-lark
    

    否则可能覆盖本地兼容补丁。

  3. 后续升级前确认新版插件已经修复:

    • Plugin SDK 子路径;
    • CJS/ESM 发布格式;
    • npm tarball 中的真实入口;
    • runtime.config.current();
    • OpenClaw 2026.9+ Runtime API;
    • Linux Docker 加载测试。
  4. 后续升级 OpenClaw 时,先备份:

    1
    2
    3
    4
    5
    
    openclaw.json
    state/
    agents/
    credentials/
    local-plugins/
    
  5. 继续处理非阻塞告警:

    • 3 条历史 Session 异常;
    • 明文模型凭据迁移到 SecretRefs;
    • Browser Chromium 路径;
    • Browser Relay legacy auth;
    • 连续失败的 Cron 自动化;
    • OpenClaw 原生备份计划。

十二、结论

这次升级的本质不是一个配置写错,而是一次跨多个版本边界的兼容性故障:

1
2
3
4
5
6
旧飞书状态
+ 旧配置字段
+ 旧 Plugin SDK
+ 错误的 npm 模块格式
+ 已变更的 Runtime API
+ Docker PID namespace 下的 SQLite ownership

最终修复路径是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
修正账号归属
→ 迁移配置字段
→ 升级插件
→ 修补 SDK 导入
→ 修正 CJS/ESM 元数据
→ 清理 import.meta
→ 迁移 Runtime API
→ 重新打包并安装依赖
→ 恢复 SQLite ownership
→ 完成 Runtime 与容器验证

最终 OpenClaw Gateway 正常启动,飞书插件成功加载,39 个飞书工具完成注册,容器保持稳定运行。

Licensed under CC BY-NC-SA 4.0