飞牛 OpenClaw 升级后报错排查记|203 字节的日志揪出一堵 Node 版本墙
最近在飞牛的应用商店里用「飞牛 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.0 和 26.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;飞牛商店只提供 Node24.15.0,且 24.15.0 正是node:sqlite存在 NUL 截断缺陷的版本,启动守卫直接拒绝运行。
三个可选方向:
- 回退到应用兼容的版本。 把
data/openclaw/package.json的依赖改回2026.5.4(也就是应用清单声明的兼容版本),重装依赖后重启应用。这跟飞牛官方声明的兼容版本一致。 - 换用受支持的 Node。 切到 Node 24.16+ 或 26.1+。但飞牛商店当前只给到
nodejs_v24= 24.15.0,需要等飞牛更新该应用,或者自行引入受支持的运行时 —— 后者属于破坏性改动,动手前务必评估。 - 不追新。 保持应用自带的
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 拉出来排个序,如果数字纹丝不动,那大概率是一堵版本墙,而不是一堆配置错。