折腾笔记

折腾笔记

飞牛 OpenClaw 升级后报错排查记|203 字节的日志揪出一堵 Node 版本墙

2026-09-20

最近在飞牛的应用商店里用「飞牛 OpenClaw」跑了一阵,一直挺稳。前几天看到应用里多了个「检查更新」,手一抖点了,把内置的 OpenClaw 从 2026.5.4 升到了 2026.9.5

升完就出事:应用界面开始报错,AI 助手相关的能力全掉。

这篇记录一下排查过程。结论先放前面:不是配置写错,也不是升级没装好,而是新版本 OpenClaw 要求的 Node 版本,飞牛这边恰好给不了。 更巧的是,卡住的那个 Node 版本号,正好是 OpenClaw 官方点名有缺陷的版本。

第一层:安装日志说"成功",说明问题不在安装

先找应用本体。飞牛的应用统一装在 /var/apps/<应用名> 下,所以:

/var/apps/trim.openclaw

安装日志在 /var/log/apps/trim.openclaw.log。读完发现全是好消息:

Installing openclaw@2026.5.4 to /vol1/@apphome/trim.openclaw/data/openclaw ...
bun add completed.
Bundled plugin postinstall completed successfully.
install_callback finished successfully.

最后一行是 finished successfully。也就是说 —— 安装环节没问题,报错发生在安装之后的运行阶段。这一步虽然没找到答案,但把排查范围缩小了一半。

同时这里也冒出了第一个矛盾点:安装日志写的版本是 2026.5.4,可我明明是在升级之后才出问题的。

第二层:找到真正的运行日志

应用自己的运行日志不在 /var/log 下,而是在应用的数据目录里:

/vol1/@appdata/trim.openclaw/info.log

一共 787 行,还在持续写入。日志尾部是这样一幅景象:

[api] -> POST /app/trim-openclaw/api/install
[api] <- POST /app/trim-openclaw/api/install 200 1ms
[runner:fail] tag="environment.preflight.config.validate" ... exitCode=1
[api] -> GET /app/trim-openclaw/api/status

应用在反复地 install → 启动前校验失败 → 再试。界面上看到的"报错",就是这个死循环的外在表现。

第三层:一个 203 字节的固定签名

把失败行单独挑出来看,会发现一个很有意思的细节:

[runner:fail] tag="environment.preflight.config.validate" ...
  exitCode=1  stdoutBytes=0  stderrBytes=203  failureKind="non_zero_exit"

值得注意的不是 exitCode=1(这个在意料之中),而是 stderrBytes=203 这个数字,从头到尾一次都没变过

而且失败的调用覆盖了所有关键环节:

environment.preflight.config.validate   启动前环境校验
channels.config.validate                渠道配置校验
channels.plugins.list                   插件列表
weixin.channels.status                  渠道状态探测

四个功能完全不同的命令,stderr 长度一模一样 —— 这基本可以断定:它们死在同一个地方,而且是在真正开始干活之前就死了。

顺着这个思路,在日志里找到了那 203 字节的真身:

openclaw: Node 24.15.0: node:sqlite truncates TEXT at embedded NUL (nodejs/node#61954); use 24.16+/26.1+ or a build with the fix
If you use nvm, run:
  nvm install 26
  nvm use 26
  nvm alias default 26

为了确认这不是巧合,我按应用的方式(以 trim.openclaw 身份、走应用自带的 CLI 包装脚本)把这条命令重新跑了一遍,把 stdout 和 stderr 分开计数:

sudo -u trim.openclaw /vol1/@appcenter/trim.openclaw/bin/openclaw config validate --json \
  > /tmp/out.txt 2> /tmp/err.txt
echo exit=$?; wc -c < /tmp/err.txt; wc -c < /tmp/out.txt

结果:

exit=1
203      ← stderr
0        ← stdout

203,一字节不差。 stdout 是空的,说明它在写任何正常输出之前就退出了。

顺手记一个细节:这段文案的第一行是 128 字节,加上换行和 nvm 提示的每一行,总共 197;差值正好 6 字节 —— 就是 nvm 那三行前面的两格缩进(3 × 2)。数得明白,心里就踏实。

一个小技巧:当日志里 stdout/stderr 的正文被截断或不完整时,长度本身就是指纹。多个不同命令报出完全相同的 stderr 长度,几乎可以直接锁定"同一个原因",比逐条读报错快得多。

第四层:node 是哪来的

报错点名了 Node 24.15.0。这台机器上 node 是谁提供的?

飞牛的应用可以声明依赖其它应用。查看应用清单:

appname          = trim.openclaw
version          = 0.1.5
install_dep_apps = nodejs_v24:bunjs>=1.3.9

nodejs_v24 是飞牛商店里的 Node.js v24 应用。查它的实际版本:

$ /var/apps/nodejs_v24/target/bin/node -v
v24.15.0

再看应用自己生成的 CLI 包装脚本 /vol1/@appcenter/trim.openclaw/bin/openclaw

export PATH="/var/apps/bunjs/target/bin:/var/apps/nodejs_v24/target/bin:...:$PATH"
exec ".../node_modules/.bin/openclaw" "$@"

关键点来了:nodejs_v24 被硬编码进 PATH,而 bunjs 目录里只有 bun 一个可执行文件、没有 node 垫片。 所以这台机器上 node 有且只有一个选择 —— 24.15.0,没有第二选项。

第五层:新版本要什么

再去看装上去的 openclaw 到底要什么:

$ cat .../node_modules/openclaw/package.json
{
  "name": "openclaw",
  "version": "2026.9.5",
  "engines": { "node": ">=24.16.0 <25 || >=26.1.0" }
}

要求 >=24.16.0,实际是 24.15.0。就差 0.0.1。

到这里,第一层留下的疑惑也解开了 —— 安装日志说 2026.5.4,磁盘上却是 2026.9.5,因为两边记录的根本不是同一个东西:

位置 含义
config/openclaw-version.env OPENCLAW_VERSION=2026.5.4 应用侧锁定的默认版本
/var/log/apps/trim.openclaw.log Pinned OpenClaw package openclaw@2026.5.4 安装时记录的锁定值
data/openclaw/package.json "openclaw": "^2026.9.5" 实际安装的依赖
node_modules/openclaw/package.json "version": "2026.9.5" 实际落地的版本

应用内那个「检查更新」改的是 data/openclaw/package.json,把它从锁定的 2026.5.4 改写成了 ^2026.9.5;而安装日志记录的是应用侧的锁定值,所以两个数字才会对不上。

文件时间戳也能确认升级落地的精确时刻:

2026-09-20 17:17:07  node_modules/openclaw/package.json
2026-09-20 17:17:08  package.json
2026-09-20 17:17:08  bun.lock

第六层:为什么偏偏 24.15.0 不行

OpenClaw 自带了一份兼容性文档 docs/install/node-compatibility.md,写得很清楚:

Node 线 状态 最低版本
Node 26 推荐 >=26.1.0
Node 24 支持 >=24.16.0 <25
Node 25 不支持
Node 23 不支持
Node 22 不支持

至于原因,文档原文大意是:Node 22.23.x、24.15.0、25.9.0、26.0.0 这几个版本的 node:sqlite TEXT 解码器,会在遇到内嵌 NUL 字符时静默截断数据。首个修复版本是 Node 24.16.026.1.0

文档里还特意强调了一句很容易被忽略的话:换成 WAL 安全的 SQLite 库并不能修复这个解码器问题 —— 换句话说,这不是"库不够新",是 Node 自身的解码器有 bug,只能换 Node。

那门槛是哪一版抬上去的?同一份文档的版本历史表里能查到:

OpenClaw 版本 Node 要求
v2026.9.3 >=24.16.0 <25 \|\| >=26.1.0
v2026.8.2 >=22.22.3 <23 \|\| >=24.15.0 <25 \|\| >=25.9.0

v2026.9.3 之前,24.15.0 是被允许的;从 v2026.9.3 起不再允许。

这就把整件事串起来了:原来的 2026.5.4 能跑,升到 2026.9.5 就必然跑不起来 —— 跟操作方式、配置内容、文件权限统统无关,是一堵纯粹的版本墙。

完整时间线

把上面所有线索按时间排一遍:

时间 事件
16:02 安装飞牛 OpenClaw 应用(连带安装 nodejs_v24 = 24.15.0、bunjs = 1.3.9),内置 OpenClaw 2026.5.4
16:41 应用升级(先卸载再安装),仍锁定 2026.5.4;网关正常启动
17:02 网关重启后 ready,渠道连接正常,一切可用
17:16:54 网关收到 SIGTERM
17:16:59 网关干净退出
17:17:08 应用内「检查更新」把 OpenClaw 升到 2026.9.5
17:17 之后 网关无法再启动;config validate / plugins list / channels status 全部 exit 1(203 字节的 Node 版本报错)

网关日志的结尾停在这里,之后再没有任何启动记录:

2026-09-20T17:16:54  [gateway] signal SIGTERM received
2026-09-20T17:16:59  [shutdown] completed cleanly in 5634ms

进程列表里也只剩应用自身的 API 服务,OpenClaw 网关进程消失了:

ps -ef | grep openclaw
trim.op+  bun /vol1/@appcenter/trim.openclaw/server/index.js

所以最终是网关起不来 + 所有 CLI 能力失效的双失效状态。

一个差点带偏方向的干扰项

日志前段还有另一类报错,看起来非常吓人:

Error: Unable to open bundled plugin public surface deepseek/provider-policy-api.js
→ Run: openclaw doctor --fix
[channels:error] failed to install channel plugin

而且 OpenClaw 自己还很"贴心"地建议你跑 doctor --fix

但它不是本次的原因。这是 16:41 那次重装之后,插件注册表迁移留下的残留 —— 日志里能看到迁移动作:

[postinstall] migrated plugin registry: 48 plugin(s) indexed

而日志往后翻,同样这项校验已经自己好了:

[channels:config.validate.parsed] valid=true  issueCount=0  hasError=false  exitCode=0

如果一开始就盯着这条去找解,跟着 doctor --fix 一路跑,很可能把原本好好的配置改坏,然后问题变得更复杂。

所以排查时有一个动作必须做:先分清"当前还在报的错"和"已经自愈的错"。判断标准很简单 —— 看它最后一次出现的位置,以及它对应的校验项当前是否已经通过。这一步省不得。

结论与处理方向

根因一句话:

应用内升级把 OpenClaw 从 2026.5.4 推到 2026.9.5,而 2026.9.5 要求 Node >=24.16.0;飞牛商店只提供 Node 24.15.0,且 24.15.0 正是 node:sqlite 存在 NUL 截断缺陷的版本,启动守卫直接拒绝运行。

三个可选方向:

  1. 回退到应用兼容的版本。data/openclaw/package.json 的依赖改回 2026.5.4(也就是应用清单声明的兼容版本),重装依赖后重启应用。这跟飞牛官方声明的兼容版本一致。
  2. 换用受支持的 Node。 切到 Node 24.16+ 或 26.1+。但飞牛商店当前只给到 nodejs_v24 = 24.15.0,需要等飞牛更新该应用,或者自行引入受支持的运行时 —— 后者属于破坏性改动,动手前务必评估。
  3. 不追新。 保持应用自带的 2026.5.4,等 nodejs 应用更新之后再来升 OpenClaw。

最后提醒一句:不要绕过这个版本检查

报错确实只是"启动被拒",看起来又烦又没道理,很容易让人想加个参数把守卫跳过去。

但千万别这么做。

这个门槛挡的不是版本号洁癖。24.15.0 的问题是 node:sqlite解码器会在内嵌 NUL 处静默截断数据 —— 加参数跳过守卫,等于把静默的数据损坏引进数据库读写里,而且不会有任何提示,等到发现时已经晚了。

"启动时明确报错" 和 "数据被悄悄写坏",显然是前者友好得多。

顺便说,飞牛其实在自己的应用描述里写过警告:

本应用默认安装并兼容 OpenClaw v2026.5.4 版本,升级 OpenClaw 可能会带来不可预料的兼容性问题,请谨慎升级,如需升级可自行在应用中检查更新升级。

而 0.1.5 版本的更新日志里,恰好新增了这一条:

3.【检查更新】支持检查 OpenClaw 版本并更新到最新版本,并放开启动时插件版本限制

官方加了按钮,也留了警告,只是警告确实不够显眼。 升级这类"应用内自带运行时"的东西之前,值得先花十秒看一眼兼容性说明 —— 这次就是这十秒没看。

附:本次用到的排查命令

全部是只读命令,按使用顺序排列:

# 1. 找应用本体
ls /var/apps/

# 2. 安装日志 —— 确认安装环节是否正常
cat /var/log/apps/trim.openclaw.log

# 3. 运行日志 —— 真正的问题在这里
cat /vol1/@appdata/trim.openclaw/info.log
grep -n -E "error|fail|exitCode" /vol1/@appdata/trim.openclaw/info.log

# 4. CLI 包装脚本 —— 看 node 是从哪来的
cat /vol1/@appcenter/trim.openclaw/bin/openclaw

# 5. 依赖声明与实际安装版本
cat /var/apps/trim.openclaw/manifest
cat /var/apps/trim.openclaw/config/openclaw-version.env
cat /vol1/@apphome/trim.openclaw/data/openclaw/package.json
cat /vol1/@apphome/trim.openclaw/data/openclaw/node_modules/openclaw/package.json

# 6. 系统里的 Node 版本
/var/apps/nodejs_v24/target/bin/node -v

# 7. 官方兼容性文档 —— 门槛和原因都在这
cat /vol1/@apphome/trim.openclaw/data/openclaw/node_modules/openclaw/docs/install/node-compatibility.md

# 8. 网关日志与进程状态
tail /vol1/@apphome/trim.openclaw/data/openclaw/gateway.log
ps -ef | grep openclaw

# 9. 精确复现 stderr(只读校验,不修改任何东西)
sudo -u trim.openclaw /vol1/@appcenter/trim.openclaw/bin/openclaw config validate --json \
  > /tmp/out.txt 2> /tmp/err.txt
wc -c < /tmp/err.txt

全程没有跑 doctor --fix,没有修改任何文件,没有重启或停用任何服务。


一句话总结:升级之后报错,先别急着改配置 —— 把失败行的 stderrBytes 拉出来排个序,如果数字纹丝不动,那大概率是一堵版本墙,而不是一堆配置错。