附上小更新:OpenClaw 2026.4.21
- ️ OpenAI Image 2
- npm 更新修复捆绑插件
- Docker E2E 覆盖通道依赖
- 低风险修复回溯
这件事的开端比想象中更荒唐
我是OpenClaw的深度用户,过去两个月里几乎让它在生产环境满负荷运转。起初我以为自己对配置理解不到位,导致性能上不去。但当我终于把CPU和内存压到理想水位后,系统开始像多米诺骨牌一样接连报错。
我在GitHub上提交了好几个详细的issue,附带复现步骤和日志片段。结果你们猜怎么着?我被封禁了。对,不是代码被拒,是人被踢出去。更讽刺的是,后来我在上游代码里看到了自己提交过的修复方案,几乎一模一样。所以bug是真的存在,只是报告bug的人本身成了问题。这不是卖惨,这是给你们提个醒:如果你也在跑OpenClaw,尤其是那种打了本地补丁、挂了自定义代理、绑了Discord、接了Codex或Kimi、还用systemd和cron做守护的复杂架构,那么升级到4.20之前,请把我下面列出的每个坑都亲手验证一遍。
网关重启时会话和绑定的丢失问题
这个问题出现在网关重启的瞬间。当系统收到重启信号后,它会粗暴地切断所有活跃会话,同时把已经建立的ACP绑定也一并丢弃。受影响的核心文件是dist/gateway-cli-Dk7XTZhh.js,同时systemd服务单元也脱不了干系。
解决方法是给网关添加SIGTERM信号排水机制。具体来说,你需要让进程在收到终止信号后先等待所有会话自然结束,再执行重启。systemd单元里的KillMode=mixed模式可以保证主进程收到信号后,子进程还能继续存活一小段时间用来清理现场。如果不做这个改动,每次重启都等于一次小型灾难。
ACP绑定在生命周期中的身份与状态丢失
很多用户不知道,ACP会话里除了消息内容,还携带身份标识和状态数据。但在旧版本中,当会话因为网关重启或网络波动而重建时,这两样东西就凭空消失了。问题代码藏在dist/persistent-bindings.lifecycle-C16AQhEC.js里。
修复的逻辑是把身份和状态数据写入持久化存储,并在每次会话重建时主动读取回来。不要指望框架自动帮你记忆,你必须显式地在生命周期钩子里保存和恢复。这就像你每次进超市都要重新刷会员卡,而不是系统记住你是谁。
关机时的脏崩溃与AgentDisconnectedError异常
当你执行systemctl stop openclaw或者直接杀死进程时,有时候会看到一个名叫AgentDisconnectedError的红色堆栈。这个错误本身不可怕,可怕的是它发生在关机流程的最后几毫秒,导致日志被截断,你根本不知道之前还有哪些资源没释放。
问题出在dist/unhandled-rejections-C1EdNFPd.js对未捕获Promise rejection的处理方式。解决方案是在进程退出前主动抑制这个错误,也就是加一个全局的process.on('beforeExit')钩子,检查到正在关机就直接吞掉异常。别觉得这种做法不优雅,稳定比优雅重要一百倍。
多轮编辑中的不匹配提示缺失
当你让OpenClaw同时修改多个文件时,它会尝试用multi-edit模式一次性应用所有改动。但如果某个文件的内容在你发起修改之后已经被外部程序改过了,那么编辑器会提示不匹配。旧版本的问题在于,它只告诉你失败,不告诉你失败的具体位置和原因。
修复文件是dist/bash-tools-UuDLD4ZI.js,改动内容是把单次不匹配检测扩展为逐个差异块检测。现在系统会明确告诉你第几个文件的第几行出了偏差,而不是甩一句“multi-edit mismatch”就装死。这就从“你的车坏了”升级到了“你的车左前轮胎压为0”。
复合Bash命令的预检查绕过
这是我最喜欢的一个安全漏洞。当你执行a && b这样的链式命令时,旧版本只检查了第一个命令a的合法性,然后直接放行整个链条。攻击者可以写无害命令 && rm -rf /,预检查看到左边无害就点头,右边直接执行了。
修复方案同样在dist/bash-tools-UuDLD4ZI.js里,改动是递归解析每个子命令并分别做安全检查。现在系统会把&&、||、;、|这些连接符拆开,每个子命令单独审核。别问为什么一开始没这么设计,问就是技术债。
pkill自匹配导致的SIGKILL循环
这是一个特别搞笑的bug。当你用pkill -f openclaw清理残留进程时,这个命令本身匹配到了自己刚启动的检查进程,然后把自己杀了。紧接着systemd发现服务挂了又开始重启,重启后又有新的检查进程,形成无限自杀循环。
修复路径在dist/bash-tools.exec-runtime-DhvVA1iE.js,解决方案是抛弃pkill改用fuser -k <端口号>/tcp。端口绑定是唯一的,不会自匹配。同时还需要强化退出处理逻辑,确保被kill的进程不会在0.1秒内重生。
ACP管理器的元数据损坏
元数据损坏听起来很高级,实际表现很简单:你的会话绑定突然失效,明明之前连得好好的Discord频道收不到任何消息。问题源头在dist/manager-ZiizW_Kh.js里,元数据在每次会话同步时被部分覆盖。
修复方式是应用生命周期保存逻辑,也就是在每次写入元数据之前先读取现有副本,只更新变化字段,不整块覆盖。这跟数据库行锁防止丢失更新是一个道理,只不过发生在一个JSON对象上。
运行环境中显示原始模型ID而不是友好名称
当你查看日志或者调用状态接口时,看到的不是gpt-4-turbo而是azure-openai-gpt4-turbo-deployment-us-east这种又长又丑的内部ID。问题在dist/selection-D0BzPQwl.js里,系统直接输出了配置中的原始键名。
修复方法是在运行时注入model.name字段。你需要在配置里给每个模型定义一个显示名称,然后让渲染层优先读取这个友好名称。不复杂,但不做的话你的监控面板会丑到没人想看。
Codex初始化时的无限挂起
Codex扩展在启动时有时候会卡住,既不报错也不继续,就那么一直转圈。调试发现初始化函数里没有设置超时,如果上游API不响应,它就永远等下去。
修复文件是dist/extensions/codex/harness-BIlt4BEY.js,改动只有一行:给初始化请求加一个60秒的默认超时。60秒后还没收到响应就主动放弃并抛出明确异常。这比挂到天荒地老要友好得多。
Kimi模型因为思考载荷被API拒绝
Kimi的API有一个严格限制:请求体中不能包含thinking字段。但OpenClaw的流式处理模块会在某些场景下自动添加这个字段,导致请求被拒。问题代码在dist/stream-CZiIS_E8.js里。
解决方案是把thinking键完全移除,注意是移除而不是置空。因为有些API网关会检查键的存在性,空值也不行。你需要在发送请求之前递归遍历载荷树,删掉所有名为thinking的节点。
OAuth刷新令牌的虚假过期警告
系统每隔几小时就打印一条警告说刷新令牌过期了,但实际上去请求新令牌是完全正常的。这个误报源于dist/auth-health-BgWiv8uG.js里的健康检查逻辑,它没有正确区分refresh_token和access_token的过期时间。
修复是让健康检查尊重refresh_token的独立生命周期。你需要检查刷新令牌本身的有效期,而不是用访问令牌的剩余时间来判断。否则就像用牛奶的保质期去判断冰箱里的鸡蛋坏没坏。
插件加载器的重复警告噪音
每次启动时,插件加载器都会对同一个问题打印好几遍警告,比如某个依赖缺失或者版本不匹配。这是因为dist/loader-B9Yrel7b.js在扫描、验证、加载三个阶段分别做了一遍相同的检查。
修复方法是将已安装的插件标记为可信来源,首次验证通过后就把结果缓存起来。后续阶段直接读缓存,不再重复检查。日志从几十行压缩到两三行,你的眼睛会感谢这个改动。
内联媒体路径在回复中无法附加文件
当你在消息里输入一个本地图片路径,期望机器人自动发送这张图时,旧版本做不到。它只把路径当作普通文本处理,文件本身没有被读取和上传。问题在dist/reply-media-paths.runtime-Q57v1N2E.js里。
修复流程分三步:检测消息中符合文件路径格式的字符串,验证文件是否存在且大小合规,然后将其暂存到上传队列。最后在生成回复时把这个文件作为附件发送出去。不要问为什么一开始没做,问就是MVP心态。
ACP流式传输的缓冲延迟
ACP会话中的消息不是实时显示的,而是积累到一定长度才一次性吐出。这种缓冲导致用户体验极差,感觉像是网络卡顿。根本原因在openclaw.json配置文件中,deliveryMode默认值是batch。
你需要手动改成live。这个改动不需要修改代码,只需要编辑配置文件。但绝大多数人不知道这个参数的存在,官方文档也藏得很深。改完之后消息会像打字机一样逐字出现,延迟从几秒降到几十毫秒。
启动时的上下文内存泄漏
每次启动时,系统会预加载大量历史上下文到内存中,包括之前会话的完整日志、绑定关系、甚至已经被删除的临时文件。这个设计导致内存使用量随时间线性增长,永不下降。控制开关在openclaw.json里。
你需要禁用startupContext这个开关,把它设为false。改完之后系统只在真正需要时才加载上下文,而不是无脑全量预读。如果你的服务器内存小于16G,这个改动能救命。
长会话的压缩策略过于激进
当会话长度超过一定阈值后,系统会频繁触发上下文压缩,有时每几轮对话就压缩一次。压缩本身很消耗CPU,而且会丢失部分细节。问题出在openclaw.json中的contextTokens默认值太小。
把contextTokens从默认的几万提升到400k。更大的上下文窗口意味着你可以容纳更多轮对话才触发一次压缩。当然这需要你的模型支持那么大的窗口,如果不支持就别乱改。
ACP长会话的超时限制为30分钟
ACP会话默认有30分钟的空闲超时,如果你的任务需要人工审批或者等待外部数据,经常超时断开。这个硬编码限制在openclaw.json里是可以覆盖的。
把acpLongTurnTimeout从1800秒增加到3600秒。一个小时足够绝大多数人工介入场景。如果你跑的是过夜任务,甚至可以设到10800秒。但注意这也会增加资源占用的风险,别设成无限大。
Kimi流式调用中工具参数为空
当你在流式模式下使用Kimi的工具调用功能时,传过去的参数对象经常是空的{},导致工具无法正常工作。这是因为Kimi的流式响应格式和标准Anthropic格式不兼容。
解决方案不是在原代码上修修补补,而是直接在openclaw.json里把Kimi的协议切换到anthropic-messages。这个设置会让系统用Anthropic的标准来解析Kimi的响应,反而能正常工作。这就像用英语语法去读德语,居然读通了。
Codex的降级级联错误
当首选模型不可用时,Codex会尝试用备选模型。但备选列表里包含了根本不存在的模型名称,或者已经被废弃的旧版本,导致降级失败。修复位置在openclaw.json中。
直接把fallbacks数组设为空[]。没有降级选项比错误的降级选项更好,因为前者会立刻告诉你失败原因,后者会让你在错误的模型上浪费半小时才发现结果不对。
过期的运行时状态未清理
随着系统运行时间增长,/tmp目录下会堆积大量临时文件,内存里也会残留僵尸进程的句柄。这些垃圾不会自动消失,因为没有定时清理任务。解决方案是写在crontab里。
添加两个cron任务:每小时清理一次超过24小时的临时文件,每天重启一次网关服务来释放内存碎片。别指望应用自己做好资源管理,写死的代码不会突然变聪明。
无限期的前台Bash执行保护缺失
当你用hook执行一个Bash脚本时,如果脚本里有一个死循环或者等待用户输入的read命令,它会永远挂住整个系统。旧版本没有保护机制。修复文件在hooks/bash-bounded-contract-pretooluse.*里。
你需要给每个Bash执行加上超时和输出大小限制。比如用timeout 30s限制最长执行时间,用head -c 10M限制输出体积。超过限制直接杀掉进程并返回错误,别让它在那里空转一整天。
Gateway的systemd重启语义不一致
有时候你用systemctl restart能正常重启,有时候会卡住,有时候会启动两份实例。这是因为systemd单元文件里没有定义清楚重启时的依赖关系和资源释放顺序。
添加systemd的drop-in覆盖文件,明确设置Restart=on-failure、RestartSec=5s以及TimeoutStopSec=30s。这些参数保证重启时先完整停止旧进程,等待端口释放,再启动新进程。不要依赖默认值,默认值就是给你挖坑的。
受污染的启动会话在开机时未清理
如果你在项目A里打开了一个会话,然后切换到项目B,启动时系统可能会把项目A的残留会话加载进来,导致配置错乱和环境变量污染。这就是交叉泄漏。
解决方法是在启动流程最开始运行invalidate-contaminated-startup-sessions.mjs脚本,它会扫描所有会话文件,检查里面的项目路径是否和当前环境匹配,不匹配的直接轮换掉。每次开机都执行一次,保证你是干净的。
Claude ACP供应商运行时的上游问题修补
Claude官方SDK里有一些bug,官方不修或者修得很慢。OpenClaw被迫在自己的代码库里包含了这些文件的副本,并打上了以OPENCLAW_*开头的标记补丁。这些补丁文件会覆盖官方版本的行为。
升级后你需要检查vendor目录下的文件有没有被覆盖。如果发现官方更新把补丁冲掉了,重新应用一遍带标记的修改。这是供应商锁定带来的代价,除非Claude自己修好,否则你得一直背着这个补丁。
Claude SDK注入的系统级提醒
Claude的SDK会在每次对话开头自动插入一段“由Claude驱动”的提醒文字,这会干扰你的纯数据交换场景。这些提醒是硬编码在SDK的某个模块里的。
你需要找到SDK安装目录下的具体文件,通常是/usr/lib/claude-code/...里的某个JavaScript文件,搜索那段提醒文字,把它替换成空字符串或者注释掉。别担心违反协议,你只是在修复一个不被需要的功能。
Claude CLI注入的系统级提醒
和SDK一样,CLI版本也有同样的提醒注入问题,而且更隐蔽,因为它是编译后的二进制文件里嵌着的。但这不代表不能改。
用十六进制编辑器打开二进制文件,搜索提醒文字的UTF-8编码,把它全部填充成空格或者\0。这种方法比较粗暴但有效。升级后需要重新做一遍,因为二进制文件被覆盖了。
Gemini工具与思考逻辑的行为漂移
Gemini的API每隔几个月会悄悄改变工具调用和思考链的返回格式,导致之前能用的代码突然失效。OpenClaw在pi-ai扩展里做了一层适配,但每次Gemini更新都会漂移。
你需要在更新后重新运行一次热修复脚本,它会对比新旧格式的差异,自动生成新的适配层。别指望一次修复永久有效,Gemini改协议比你们换手机还勤快。
Claude CLI的ACP运行时崩溃与自愈
Claude CLI在ACP模式下运行几小时后会莫名其妙崩溃,错误信息指向内存分配失败。这看起来是CLI自己的bug,但OpenClaw提供了一个自愈守护进程。
安装extensions/claude-cli-runtime-heal/目录下的守护脚本,它会每10分钟检测一次CLI进程的健康状态,发现崩溃就立即重启并恢复会话。这不是根治,这是用运维手段掩盖开发缺陷。
ACP运行时的自愈与绑定持久化组合
最后一个问题是前面多个问题的组合:自愈守护进程重启了CLI,但重启后之前的ACP绑定全部丢失,你需要重新授权。这显然不行。
安装扩展extensions/lossless-claw@0.2.5,它提供了一个持久化层,在CLI重启前把绑定关系写入磁盘,重启后再原样恢复。整个过程对用户透明,你甚至感觉不到崩溃发生过。
升级后必须运行的审计命令
现在你升级到了4.20,不要假设一切正常。你需要手动审计以下每个部分:
- 已安装的dist文件是否包含了上述所有修复;
- openclaw.json里的配置键是否都按表格中的建议修改了;
- systemd的单元和drop-in文件是否就位;
- hooks目录下的Bash边界保护脚本是否存在;
- cron任务里是否添加了临时文件清理;
- 日志中是否还有未消失的旧警告;
- 备份目录里是否保留了可回滚的版本;
- 本地扩展是否与新版核心兼容。
运行这个提示词:针对上述修复清单,按受影响的子系统、配置键、运行时行为和文件路径,逐项审计你的实际安装。