欢迎光临
我们一直在努力

OpenClaw 装过 25 个依赖补丁,5 月 10 日一夜之间全部撤走:13 小时锁定真空与今天的零覆盖状态

OpenClaw 装过 25 个依赖补丁,5 月 10 日一夜之间全部撤走:13 小时锁定真空与今天的零覆盖状态

两年前主流前端项目还在为 ^ 范围头疼的时候,OpenClaw 在 2026 年 1 月 30 日就干了件更彻底的事:在 package.json 里塞进一块 25 条目的 pnpm.overrides,把包括 qs、tar、axios、uuid 在内的一整批传递依赖钉死在精确版本上。

这块补丁在 npm registry 里连续存在了 152 个版本、约 4 个月。然后在 2026 年 5 月 10 日到 11 日之间,它被拆掉了。此后 125 个连续版本,OpenClaw 的包元数据里再也没有出现过 pnpm 这个字段。

本文拆的是这段安装期依赖钉死层的完整生命周期。所有数字来自 2026 年 10 月 18 日 06:0x 对 registry.npmjs.org/openclaw 全量元数据的解析(278 个版本,非抽样),以及 unpkg 上历史版本 package.json 的原文比对。

一、先说结论:一个字段的四段生命周期

先给整体轮廓,后面每一段都会给出可复算的口径。

阶段 版本区间 pnpm.overrides 顶层 overrides packageManager
引入期 2026.1.29-beta.1 → 2026.5.10-beta.2(152 个) 3 → 25 条 1 → 7 条 pnpm 10.23.0 → 10.33.2
真空期 2026.5.10-beta.3 → 2026.5.10-beta.5(3 个) 已删除 保留 7 条 pnpm 11.0.8
收缩期 2026.5.10-beta.5 → 2026.5.12-beta.1(约 26 小时) 已删除 保留 7 条 依赖里的 ^ 同时归零
现状 2026.6.2-beta.1 → 2026.10.4(71 个) 无 无 无

四段之间最值得停一下的是第二段。pnpm.overrides 在 2026.5.10-beta.2 还钉着 22 个包,紧接着的 2026.5.10-beta.3 就没了——而同一时刻 dependencies 里还有 37 个 ^ 浮动范围没被改成精确版本。这是整个时间线里唯一一段「钉死层已撤、浮动层未补」的窗口。

二、这段字段到底钉了什么

先看 2026.5.10-beta.2 的 pnpm.overrides 全文(22 个非别名条目,另有 3 个别名条目已在下文单列)。这是 unpkg 上该版本 package.json 的原文:

openclaw 2026.5.10-beta.2 的 package.json,pnpm.overrides 区块

把这份清单和同版本的 dependencies 对照,一张更有信息量的表就出来了:

被钉的包 出现在 dependencies? 钉死版本
@anthropic-ai/sdk 是(直接依赖) 0.95.1
tar 是 7.5.15
minimatch 是 10.2.5
file-type 是 22.0.1
typebox 是 1.1.38
@aws-sdk/client-bedrock-runtime 是 3.1045.0
qs 否 6.14.2
axios 否 1.16.0
form-data 否 2.5.4
uuid 否 14.0.0
yauzl 否 3.2.1
protobufjs 否 7.5.5
tough-cookie 否 4.1.3
path-to-regexp 否 8.4.0
fast-xml-parser 否 5.7.0
hono / @hono/node-server 否 4.12.18 / 1.19.14
defu / basic-ftp / ip-address / follow-redirects / fast-uri 否 6.1.5 / 6.0.1 / 10.2.0 / 1.16.0 / 3.1.2

22 个钉死目标里,只有 6 个是直接依赖,另外 16 个是纯传递依赖。

这一点是理解整件事的关键。dependencies 里写着 ^ 的包,你的包管理器至少知道该去哪个 semver 区间找;但 qs、axios、form-data 这些根本不写在 dependencies 里——它们是被别的包拖进来的第二级、第三级依赖,版本完全由上游包的声明决定。OpenClaw 用 overrides 把这 16 个钉死,等于在正常解析链之外强行开了一条「我说了算」的通道。

顺带一提,钉死的对象本身是有历史包袱的:qs(原型污染)、tar(解压路径穿越)、minimatch(ReDoS)、axios/form-data(SSRF 与泄露)都是反复出现在历史漏洞通告里的包。给这类包钉版本,是当时最常规也最有效的纵深防御动作。

2.1 别名钉法:npm: 与 $ 两种特殊语法

清单里有三种写法不是单纯的「版本号」,容易被当成噪声跳过:


"request": "npm:@cypress/request@3.0.10",
"request-promise": "npm:@cypress/request-promise@5.0.0",
"node-domexception": "npm:@nolyfill/domexception@1.0.28"

这是 npm 的包别名语法:装 @cypress/request,但在依赖树里以 request 的名义存在。request 是 2020 年就停止维护的老包,很多老包还在依赖它;OpenClaw 选择把它的实现整体换成 Cypress 维护的 fork,而不是继续修一个死项目。同样的手法用在 node-domexception 上,目标包是 @nolyfill/domexception。

node-domexception 还有个细节:它先被钉成 npm:@nolyfill/domexception@^1.0.28(带浮动范围),覆盖 50 个版本(2026-03-02 → 04-21),之后才收紧成 @1.0.28 精确版,覆盖后 62 个版本(04-22 → 05-10)。同一个 override 目标,先浮动后收紧,这条改动在 git 历史之外没有任何地方说明过原因。

第三种写法出现在顶层 overrides 里:

openclaw 2026.5.10-beta.2 的 package.json,顶层 overrides 与 packageManager 并存

"@aws-sdk/client-bedrock-runtime": "$@aws-sdk/client-bedrock-runtime" 是 self-reference 语法,意思是「和 dependencies 里声明的版本保持一致」。它的作用是:让 overrides 能显式覆盖掉某个 pnpm.overrides 已经管过的包,而不必去猜具体版本号。

2.2 🔴 关键发现:两套 overrides 在 96 个版本里内容从不一致

package.json 支持两种 overrides:pnpm.overrides(pnpm 专用)和顶层 overrides(npm 通用)。OpenClaw 在 96 个版本里同时写了这两个块,时间从 2026.1.29-beta.1 到 2026.5.10-beta.2。

逐版本比对这 96 个版本的两块内容,结果是:0 个版本内容完全相同,96 个全部不同。

在最后一个双块版本 2026.5.10-beta.2 上:

  • pnpm.overrides 有 25 个条目(22 个普通 + 3 个别名/自引用)
  • 顶层 overrides 只有 7 个条目
  • 顶层有的 7 个键,全部也是 pnpm.overrides 的键(无独有键)
  • 6 个键两边取值相同(如 axios: 1.16.0、uuid: 14.0.0、fast-uri: 3.1.2)
  • 1 个键取值冲突:@aws-sdk/client-bedrock-runtime 在 pnpm 侧是 3.1045.0,在顶层是 $@aws-sdk/client-bedrock-runtime
  • 18 个键只存在于 pnpm.overrides(qs、tar、hono、protobufjs 等)

也就是说,顶层 overrides 是 pnpm overrides 的一个更小的子集,且对同一个包用了不同的钉法。这带来一个直接后果:

用 npm 装 OpenClaw,拿到的是 7 条覆盖规则;用 pnpm 装,拿到的是 25 条。换包管理器会换掉 16 个传递依赖的实际版本。

这不是 bug——$ 自引用在 npm 和 pnpm 下的语义解析路径不同,官方文档也建议只写一套。但 registry 元数据里没有任何字段提示用户这件事,而用户默认会用 npm。

三、13 小时锁定真空:撤补丁和补精确版本不是同一件事

把 2026-05-08 到 2026-05-14 的版本逐个铺开,能看到一段非常密集的连续改动:

版本 发布时间(UTC) pnpm.ov top.ov deps 带 ^ 的依赖数 packageManager
2026.5.9-beta.1 05-09 13:27 25 7 61 37 pnpm@10.33.2
2026.5.10-beta.1 05-10 13:59 25 7 61 37 pnpm@10.33.2
2026.5.10-beta.2 05-10 18:15 25 7 61 37 pnpm@10.33.2
2026.5.10-beta.3 05-11 03:28 0 7 61 37 pnpm@11.0.8
2026.5.10-beta.4 05-11 16:03 0 7 61 38 pnpm@11.0.8
2026.5.10-beta.5 05-11 16:38 0 7 62 39 pnpm@11.0.8
2026.5.12-beta.1 05-12 17:09 0 7 62 0 pnpm@11.1.0

看最后两列的交叉点:

2026-05-11 03:28 到 16:38,共 13.2 小时、3 个版本,这三个版本同时满足两个条件——pnpm.overrides 已被删除,而 dependencies 里的 ^ 浮动范围仍有 37~39 个。这是全时间线里唯一一段「传递依赖既不被 overrides 钉死、也不被精确版本约束」的窗口。全量扫描确认,符合这个条件的版本恰好就是这 3 个,再无第四个。

到 2026.5.12-beta.1(05-12 17:09),dependencies 里的 39 个 ^ 一次性归零,packageManager 同时跳到 pnpm 11.1.0。此后 122 个连续版本、全部 65 个直接依赖、全部 79 个 optionalDependencies,无一条浮动范围。

所以真相是:这套钉死机制不是被「删除」,而是被「替换」了。 旧方案是 overrides 钉传递依赖 + 直接依赖带浮动范围;新方案是所有直接依赖锁死到三位数精确版本。两者目标相同——确定性安装——手段完全不同。

值得留意的是替换过程中的一次反向回退:2026.5.10-beta.4 的带 ^ 依赖数从 37 涨到 38,.beta.5 又涨到 39。在整体收紧的窗口里出现依赖数增长,说明当时正在增删依赖,还没走到收紧那一步。

packageManager 字段本身也经历了 7 次变更:

生效版本 pnpm 版本 距上一个变更
2026.1.29-beta.1 10.23.0 —
2026.3.24-beta.1 10.32.1 54 天
2026.4.19-beta.1 10.33.0 24 天
2026.4.29-beta.1 10.33.2+sha512 11 天
2026.5.10-beta.3 11.0.8+sha512 10 天
2026.5.12-beta.1 11.1.0+sha512 1 天
2026.5.22-beta.1 11.2.2+sha512 10 天

主版本号 10 → 11 的跳跃恰好发生在 overrides 被删的那一个版本上。两者是同一次改动里的两个动作,还是先后无关的两次变更,registry 元数据没有任何字段能区分,此处记为 UNKNOWN。

四、今天的 OpenClaw 到底还剩什么

把当前最新稳定版 2026.10.4(2026-10-15T23:58:28.695Z 发布)的元数据摊开,钉死层是彻底空的:

字段 2026.10.4 状态
pnpm ABSENT(最后一次出现:2026.5.10-beta.2,158 天前)
overrides ABSENT(最后一次:2026.6.2-beta.1,134 天前)
packageManager ABSENT(最后一次:2026.7.1-2,89 天前)
onlyBuiltDependencies ABSENT(最后一次:2026.5.10-beta.2)
dependencies 65 条,全部精确版本,零 ^ 零 ~
optionalDependencies 79 条,全部精确版本
devDependencies 62 条,全部精确版本
bundleDependencies 3 条:@openclaw/proxyline、chrome-devtools-mcp、undici
peerDependencies ABSENT(历史上出现过 93 个版本后归零)

下面这张图是 2026.10.4 的 package.json 在同一个滚动位置上的样子——对照上面 2026.5.10-beta.2 的截图,optionalDependencies 后面没有 overrides,engines 后面没有 packageManager:

openclaw 2026.10.4 的 package.json,同一位置已无 overrides 与 packageManager

4.1 那 16 个传递依赖现在归谁管

这是切换方案最实际的一个后果,也是这套分析里最需要读者自己判断的地方。

2026.5.10-beta.2 时期,qs、axios、form-data、uuid、protobufjs、tough-cookie、yauzl 等 16 个传递依赖的版本由 OpenClaw 的 overrides 单方面决定。今天这些 overrides 不存在了,它们的实际版本改为由上游包各自的 semver 声明决定——也就是由 hono、express 系包、各类 SDK 的依赖树决定。

从理论上说,这降低了维护成本(上游发新版能自动跟上)。但代价同样明确:OpenClaw 不再有能力在某个传递依赖出现安全问题或行为回归时,用一次 overrides 改动把全站用户挡在门外。这个取舍是有意的还是被迫的,本文不做推断,记为 UNKNOWN。

可以确定的是:当前所有直接依赖都是精确版本,安装结果是可复现的。这一点上今天的 OpenClaw 比 5 月之前更严格,不是更松。

4.2 一个需要修正的直觉

常见的说法是「npm 包有 lockfile,所以传递依赖被锁住了」。对项目成立,对发布的库不成立。

registry 元数据里的 _hasShrinkwrap 字段可以佐证:278 个版本里只有 62 个为 True,且这 62 个极度碎片化——2026-08-10、2026-09-11、2026-09-20、2026-09-23 各出现一次立刻又变回 False。2026.10.4 的值是 False。

也就是说,OpenClaw 这个发布包绝大多数时候不携带 shrinkwrap。npm 的 shrinkwrap 本来也是给应用(application)用的,对库(library)会覆盖下游的依赖树判断,很少有库这么做。所以下游装 OpenClaw 时,传递依赖的实际解析结果由下游自己的 lockfile 决定,OpenClaw 侧无法约束。

这也反过来解释了为什么当年要引入 overrides:在一个不携带 shrinkwrap 的发布包里,这是唯一能约束传递依赖版本的手段。

五、给使用者的三条可操作结论

一、你现在装到的依赖树,与 5 月之前的 OpenClaw 不是同一棵树。 钉死的 16 个传递依赖现在由上游决定,两次安装同一版本 OpenClaw,如果 lockfile 之外的上游发过新版,实际拿到的传递依赖可能不同。

二、选包管理器会影响历史版本的结果,但不影响今天。 2026.5.10-beta.2 那个时期,npm 和 pnpm 拿到的覆盖规则是 7 条 vs 25 条。今天三个钉死字段全部为空,两个包管理器看到的元数据一致,这条差异已经消失。

三、要验证而不是相信。 直接打开 unpkg 上的 2026.5.10-beta.2 package.json 和 2026.10.4 的对应文件,搜 overrides 和 packageManager 两个键,就能自己确认本文的时间线。

六、证据口径与已知未知

本文所有数字的采集与计算方式:

  • 主数据源:https://registry.npmjs.org/openclaw,2026-10-18 06:0x 采集,响应体 19,886,890 字节,time.modified 为 2026-10-15T23:58:29.036Z。278 个版本全量解析,非抽样。
  • 交叉核对源:unpkg CDN 上 2026.5.10-beta.2(99,397 字节)与 2026.10.4(148,587 字节)两个版本的 package.json 原文,与 registry 元数据逐字段一致。
  • 截图:三张均为 unpkg 页面的真实渲染截图,Chromium 无头模式 1280px 视口、DPR=2,定位到目标键所在行后逐张目视确认内容非 404、非空白、非挑战页。

必须声明的未知项:

  1. UNKNOWN:pnpm 10 → 11 的主版本跳跃与 overrides 删除是否属于同一次改动,registry 无字段可判定。
  2. UNKNOWN:node-domexception 的 override 先浮动后收紧的动机,官方无说明。
  3. UNKNOWN:2026-06-03 之后顶层 overrides、2026-07-18 之后 packageManager 分别移除的决策原因。
  4. UNKNOWN:全部结论均基于包元数据的声明,未执行任何安装、未跑依赖审计工具、未做供应链溯源。
  5. 口径:本文的「传递依赖」= 在该版本的 dependencies 中不存在、但出现在 pnpm.overrides 中的条目。该定义基于包元数据,不等于运行时实际依赖树的完整拓扑。
  6. 口径:^ 计数按字符串包含 ^ 统计,未区分 ^ 与 ^= 等边界情况;全量扫描中未出现此类写法。

七、与站内其他 OpenClaw 内容的分工

文章 切入层 与本文的边界
《OpenClaw npm 278 版本台账:Node 引擎区间在 9 月 8 日整体换掉》 registry 元数据的版本与安装契约层 该文覆盖 dist-tags、engines、依赖浮动范围的总体时间线;本文只深挖 pnpm/overrides/packageManager 这三个安装期钉死字段的内部结构与生命周期,两者数据源相同、切入字段不重叠。
《OpenClaw 安装体积台账:LTS 371.9 MiB vs latest 245.9 MiB》 registry 元数据的交付物形态层 该文关注 unpackedSize / fileCount / worker tarball;本文关注依赖版本如何被约束,不涉及体积。
《OpenClaw 模块面:346 条 exports 子路径与 38.4% 的消失率》 registry 元数据的模块解析层 该文关注 exports 字段;本文关注的三个字段均不参与模块解析。
《OpenClaw GHSA 100 条安全公告授权审计》 官方安全公告层 该文基于 GHSA 一手公告;本文的「哪些包值得钉」是元数据层面的推断,未对照任何 CVE 编号,因此两文的安全结论不可互相印证。

八、来源

本文所有统计均由脚本对 registry 全量元数据计算得出,标题中的每个数字都有对应的正文表格或段落支撑。未执行安装、未调用任何需要认证的端点、未访问官方仓库的内部提交记录。

赞(0)
未经允许不得转载:时间网 » OpenClaw 装过 25 个依赖补丁,5 月 10 日一夜之间全部撤走:13 小时锁定真空与今天的零覆盖状态

更好的WordPress主题

支持快讯、专题、百度收录推送、人机验证、多级分类筛选器,适用于垂直站点、科技博客、个人站,扁平化设计、简洁白色、超多功能配置、会员中心、直达链接、文章图片弹窗、自动缩略图等...

联系我们联系我们

登录

找回密码

注册