# OpenClaw升级踩坑实录：从"升级即翻车"到双层防护体系

> **摘要**：记录2026年6月至9月期间6次OpenClaw升级遭遇的连环问题——从systemd硬编码路径、pnpm归属丢失、插件SDK breaking change，到GatewayDrainingError死循环。每一次升级都踩出新的坑，最终沉淀为"ExecStartPre自动清理 + admission源码补丁"的双层防护体系。这不是一份升级教程，而是一份用真金白银换来的避坑指南。

---

## 引言：为什么升级这么难？

2026年9月18日下午3点，一位客户在企业微信客服通道发消息咨询青蟹。蟹蟹没有回复。

日志里的错误信息冷冰冰的：

```
GatewayDrainingError: Gateway is draining; new tasks are not accepted
```

这不是第一次了。从6月到9月，每次OpenClaw升级都像开盲盒——你永远不知道会踩到什么。systemd服务文件指向旧路径、包管理器归属信息丢失、插件API breaking change、SQLite残留记录导致draining死循环……

老林说了一句话：

> "升级前它是好的，升级后它就不好了。"

本文记录这条升级链路上的6个真实坑案，以及最终建立的双层防护体系。

---

## 坑案一：systemd硬编码版本路径（2026年6月17日）

### 现象

老林在微信上执行升级，CLI显示成功，但Gateway启动后仍然加载旧版本代码。新功能不可用，旧bug还在。

### 诊断

检查systemd服务文件：

```ini
[Service]
ExecStart=/usr/bin/node .../openclaw/2026.6.5/.../dist/index.js gateway --port 11589
```

路径写死了`2026.6.5`。pnpm升级后新版本装在`2026.6.8`路径下，但service文件没更新——Gateway进程加载的还是老代码。

### 根因

OpenClaw通过pnpm global安装，每次升级创建新的store路径（带版本号hash）。`openclaw gateway install`生成的systemd service文件把当时的完整路径硬编码进`ExecStart`。升级后如果不重新install，service文件指向的还是旧路径。

### 修复

手动更新service文件中的3处版本信息（Description、ExecStart、OPENCLAW_SERVICE_VERSION），reload + restart。

### 教训

**升级不是一条命令的事。**CLI升级只换包，不管service文件。这是后续每次升级的1号检查项。

---

## 坑案二：openclaw命令权限指向root（2026年7月22日）

### 现象

执行`openclaw`命令时报权限错误，无法正常运行。

### 诊断

```bash
$ which openclaw
/usr/bin/openclaw

$ ls -la /usr/bin/openclaw
lrwxrwxrwx 1 root root ... /home/ubuntu/.nvm/.../openclaw
```

软链接指向root用户目录下的nvm路径，ubuntu用户无权访问。

### 根因

某次安装过程中使用了sudo，导致软链接创建到root的nvm路径下，而非ubuntu用户的pnpm路径。

### 修复

重新创建软链接，指向正确的ubuntu用户pnpm路径。

### 教训

**不要用sudo装openclaw。**pnpm global安装是用户级操作，sudo会搞乱路径归属。

---

## 坑案三：包管理器归属丢失（2026年9月8日）

### 现象

执行`openclaw update`直接报错：

```
Error: package manager owner is unknown
```

系统未做任何更改，升级被拒绝。

### 诊断

OpenClaw的更新机制需要检测当前安装是由哪个包管理器（npm/pnpm/Bun）装的。这台机器最初通过pnpm global安装，但检测不到pnpm的所有权信息了。

排查发现：pnpm版本升级后home store发生了迁移，导致包的归属元数据丢失。OpenClaw无法确认"这个包是谁装的"，拒绝更新。

### 修复

9月17日诊断确认根因后，9月18日通过pnpm直接安装指定版本绕过检测：

```bash
pnpm add -g openclaw@2026.9.4
```

然后强制重装service：

```bash
openclaw gateway install --force
```

### 教训

**pnpm升级可能丢归属。**这不是OpenClaw的bug，是pnpm的home store迁移副作用。如果遇到`package manager owner is unknown`，直接用`pnpm add -g openclaw@<版本>`绕过。

---

## 坑案四：插件SDK breaking change（2026年9月8日）

### 现象

升级到2026.9.2后，wecom-kf插件（v2026.7.1）报错：

```
ERR_PACKAGE_PATH_NOT_EXPORTED
```

龙虾教官的客服通道完全不通。

### 诊断

OpenClaw 2026.9.2移除了`./plugin-sdk`裸导出，改为`./plugin-sdk/core`子路径导出。wecom-kf插件还在用旧的import方式：

```js
// 旧（9.2之前可用）
import { ... } from "openclaw/plugin-sdk";

// 新（9.2要求）
import { ... } from "openclaw/plugin-sdk/core";
```

### 修复

手动给wecom-kf插件打补丁，修改import路径：

```bash
# 找到插件文件
grep -rl "openclaw/plugin-sdk" ~/.openclaw/plugins/wecom-kf/

# 替换为子路径
sed -i 's|"openclaw/plugin-sdk"|"openclaw/plugin-sdk/core"|g' <plugin-file>
```

### 教训

**每次升级前检查插件兼容性。**OpenClaw的`openclaw doctor`会报告插件兼容性状态。另外，这个补丁在`openclaw update`后会被覆盖（新版本路径换了），需要重新打。

后来在2026.9.4升级时，我们写了一个专用技能文件`openclaw-plugin-compat-repair`来标准化这个修复流程。

---

## 坑案五：升级副作用——数据索引消失（2026年9月9日）

### 现象

wecom-kf插件修好了，但chatreview同步任务从9月9日起连续失败。客户的聊天记录无法同步到本地分析系统。

### 诊断

chatreview同步脚本依赖`sessions.json`索引文件来定位会话数据。OpenClaw 2026.9.2升级后，不再生成这个文件——改为SQLite数据库存储。

脚本在文件系统里找不到索引文件，直接报错退出。

### 修复

将同步脚本从读取`sessions.json`改为直接扫描`.jsonl`文件。这是一个两步修复：

1. JS脚本改用目录扫描替代索引文件读取
2. wrapper脚本清理旧的`sessions.json`检查逻辑

修复后手动运行成功，同步了20个客户255条消息。

### 教训

**升级可能改变数据存储方式。**CLI版本升级不只影响可执行文件，还可能改变运行时数据格式。依赖内部实现细节的脚本需要在升级后验证。

---

## 坑案六：GatewayDrainingError死循环（2026年9月18日）

这是最严重的一个坑——升级后客户消息全部被拒，而且反复重启都修不好。

### 现象

升级到2026.9.4后，Gateway启动正常，4个渠道全部OK。但客户消息进来时全部报错：

```
[wecom-kf] [ERROR] dispatchTranscriptTurn failed:
GatewayDrainingError: Gateway is draining; new tasks are not accepted
```

重启Gateway——还是一样。再重启——还是一样。

### 诊断

这是两个独立根因叠加的结果。

#### 根因A：stale SQLite handoff记录

OpenClaw在重启前会向SQLite写入一条`gateway_restart_handoff`记录，包含当前PID和60秒TTL。新进程启动时读取并消费这条记录。

问题出在：9月8日升级失败时，进程在draining过程中被强杀（kill -9 / systemd超时），handoff记录没被消费。10天后重启，新进程读到这条旧记录——PID不匹配，TTL早过期——但代码仍然认为自己还在draining。

```
created: 2026-09-08T15:41:07  (10天前)
expires: 2026-09-08T15:42:07  (10天前就过期了)
PID: 2866622  (早就没了)
当前PID: 1995342  (完全不匹配)
```

于是新进程永远卡在draining状态，拒绝所有消息，直到手动清理这条记录。

#### 根因B：AsyncLocalStorage传播bug

清理handoff记录并重启后，wecom-kf消息仍然被拒。这次根因不同——是Node.js的AsyncLocalStorage在Promise链中传播已释放的admission context。

机制如下：

1. OpenClaw为每个HTTP请求创建`root work admission`，通过`AsyncLocalStorage.run()`传播
2. wecom-kf插件的webhook处理函数立即返回HTTP 200，但`processKfEvent`被延迟执行
3. OpenClaw调用`admission.release()`标记释放
4. 延迟任务执行时，`AsyncLocalStorage.getStore()`返回的是已释放的admission（`released: true`）
5. `isGatewaySubordinateWorkAdmissionClosed()`检查到`current.released === true`，返回`true`——误判为draining

源码对比（9.2和9.4的upstream代码完全一样，都有这个bug）：

```js
// upstream（有bug）
function isGatewaySubordinateWorkAdmissionClosed() {
    if (...) return true;
    const current = currentRootWork.getStore();
    if (current) return current.released;  // released=true → 误判为draining
    ...
}
```

### 修复

**根因A**：清理SQLite中的stale记录 + 删除JSON handoff文件 + 重启。

**根因B**：修改`isGatewaySubordinateWorkAdmissionClosed`函数，让已释放的admission回退到全局`suspendPhase`检查而非直接返回`true`：

```js
// 补丁后
function isGatewaySubordinateWorkAdmissionClosed() {
    if (...) return true;
    const current = currentRootWork.getStore();
    if (current && !current.released) return false;     // 未释放 → 放行
    if (current && current.released)                     // 已释放 → 看全局状态
        return suspendPhase !== "accepting";
    return suspendPhase !== "accepting";
}
```

### 教训

**一个症状可能有两个根因。**清理了第一个根因后症状仍在，不要怀疑修复不对——可能还有第二个独立的bug叠加。另外，`setImmediate`在Node.js 22+上不足以切断AsyncLocalStorage传播，这个问题在官方文档里没有提及。

---

## 双层防护体系

经历这6个坑之后，我们建立了两层防护，确保升级后的常见问题自动修复，不再需要人工介入。

### 第一层：systemd ExecStartPre（防stale handoff）

在Gateway服务文件中添加启动前清理：

```ini
[Service]
# 启动前自动清理stale handoff记录
ExecStartPre=/usr/bin/node -e "const {DatabaseSync}=require('node:sqlite');try{const db=new DatabaseSync('/home/ubuntu/.openclaw/state/openclaw.sqlite');db.prepare('DELETE FROM gateway_restart_handoff').run();db.prepare('DELETE FROM gateway_restart_intent').run();db.close();console.log('[ExecStartPre] stale handoff cleaned')}catch(e){console.log('[ExecStartPre] no handoff to clean')}"
ExecStartPre=/bin/rm -f /home/ubuntu/.openclaw/gateway-supervisor-restart-handoff.json
ExecStart=/usr/bin/node ... openclaw ... gateway --port 11589
```

**效果**：无论上一个进程怎么死的（kill -9、断电、崩溃），新进程启动前handoff记录已被清掉。从源头杜绝draining死循环。

### 第二层：admission源码补丁（防AsyncLocalStorage误判）

在OpenClaw的`gateway-work-admission-*.mjs`文件中打补丁。补丁内容见坑案六。

**查找文件**（路径随版本变化）：

```bash
grep -l "isGatewaySubordinateWorkAdmissionClosed" \
  ~/.local/share/pnpm/store/v11/links/@/openclaw/<version>/*/node_modules/openclaw/dist/gateway-work-admission-*.mjs
```

**效果**：deferred的wecom-kf任务即使继承了已释放的admission context，也不会被误判为draining。

### 升级Checklist

两层防护都会在升级时丢失，需要重新部署：

| 步骤 | 命令 | 说明 |
|------|------|------|
| 1. 升级前 | `openclaw doctor` | 检查插件兼容性，记录当前状态 |
| 2. 升级 | `pnpm add -g openclaw@<版本>` | 绕过package manager owner检测 |
| 3. 重装service | `openclaw gateway install --force` | 更新systemd文件中的路径 |
| 4. 重新添加ExecStartPre | 编辑service文件 | `openclaw gateway install --force`会覆盖 |
| 5. 重新打admission补丁 | `sed`或手动编辑 | 新版本=新文件 |
| 6. 检查wecom-kf补丁 | `grep "plugin-sdk/core"` | 确认import路径补丁还在 |
| 7. daemon-reload + restart | `systemctl --user daemon-reload && systemctl --user restart openclaw-gateway` | 应用所有变更 |
| 8. 验证 | `openclaw status` + 检查渠道 | 确认4个渠道OK、无draining错误 |

---

## 给同行的3条经验

### 1. 升级不是一条命令

`openclaw update`只换CLI包。systemd service文件、插件补丁、admission补丁——这些都需要手动跟进。把升级Checklist固化下来，每次照着走。

### 2. 一个症状可能有两个根因

清理了stale handoff记录后draining还在？不要慌。可能是AsyncLocalStorage传播bug在deferred任务上独立触发。诊断时看日志里的错误来源：如果`admission closed: restart drain`来自全局状态，是根因A；如果来自`current.released`，是根因B。

### 3. 用systemd的ExecStartPre做兜底

不要指望进程总是优雅退出。断电、OOM、systemd超时强制杀死——这些都会发生。ExecStartPre是systemd原生的启动前钩子，比写额外的监控脚本简单100倍。每次启动自动清理，零成本兜底。

---

## 结语：升级是一场战役

从6月17日第一次踩systemd硬编码路径，到9月18日建立双层防护体系，3个月6个坑。每个坑的根因不同——有的是OpenClaw的设计缺陷（handoff记录不随进程死亡自动清理），有的是Node.js运行时的特性（AsyncLocalStorage传播），有的是包管理器的副作用（pnpm home store迁移），有的是Breaking Change（plugin-sdk路径变更）。

但最终，所有踩过的坑都沉淀为了可复用的防护措施和检查清单。这不是"踩坑记录"，这是**用真金白银换来的运维知识资产**。

老林说：

> "先把业务想清楚再让AI学业务。"

升级也一样——先把坑想清楚，再点那个update。

---

*本文记录的所有问题均来自塘口拾鲜（台州）科技有限公司的真实生产环境。敏感信息已脱敏。*
