一、先说结论:你装的每一版 OpenClaw,npm 都会在你机器上执行两段代码
装 OpenClaw 的那一刻,npm 会自动在你的机器上跑两段代码,这件事在 npm 官方文档里有明确定义,和 OpenClaw 自己的文档没有任何关系。
npm 官方把这类脚本定义为生命周期脚本(life cycle scripts),并明确规定了它们的触发时机。真正会在你 npm install 的时候、在你自己的机器上、带着你自己的权限执行的只有三个:preinstall、install、postinstall。npm 官方文档在这三者之前还额外列了 prepare,但文档同时写明 prepare 在 npm install 场景下只在本地包、无 node_modules 的情况下才跑,对已发布的包不生效。所以对「装一个已发布的 OpenClaw」这个动作而言,实际执行的消费端钩子是三个而不是四个。
按 npm 官方文档给出的顺序,npm install 的钩子执行次序是:preinstall → install → postinstall → prepublish → prepare → prepare(重复一次)→ postprepare。文档原文写的是 “These all run after the actual installation of modules into node_modules, in order, with no internal actions happening in between”——即这几个钩子按序执行,中间不会插入 npm 的其他内部动作。

那么 OpenClaw 声明了哪几个?从 npm registry 的 package.json 元数据看,最新稳定版 2026.10.4 声明了 两个,而不是三个:
| 钩子 | 是否声明 | 声明的命令 | 归属 |
|---|---|---|---|
preinstall |
是 | node scripts/preinstall-package-manager-warning.mjs |
消费端 |
install |
否 | — | — |
postinstall |
是 | node scripts/postinstall-bundled-plugins.mjs |
消费端 |
也就是说,install 这个环节是空的,剩下两个都是 node 脚本。全文检索 278 个版本的元数据,install 钩子一次都没有出现过(0/278),这是本次全量扫描中唯一一个「始终为零」的消费端钩子。
这两个脚本具体做了什么,属于包元数据无法回答的部分——脚本内容不在 package.json 里,也不在 registry 的 tarball 索引里。本文只声明「这两个钩子存在,且会执行哪条命令」,不声明它们的行为。
二、全量扫描:195 个版本有 preinstall,222 个有 postinstall,而 install 永远是 0
本文的数据基础是 npm registry 上 openclaw 这个包的全部 278 个版本的元数据,一次性抓取、全量解析,没有抽样。抓取时间是 2026-10-18(Asia/Shanghai 下午),响应体约 19.9 MB。
按消费端钩子组合统计,278 个版本分成三组:
| 钩子组合 | 版本数 | 占比 | 覆盖版本区间 |
|---|---|---|---|
preinstall + postinstall |
195 | 70.1% | 2026.4.15-beta.1 ~ 2026.10.4 |
仅 postinstall |
27 | 9.7% | 2026.1.29-beta.1 ~ 2026.4.14 |
| 三者全无 | 56 | 20.1% | 0.0.1 ~ 2026.3.28 |
消费端钩子的时间线本身有两段:
- 2026-01-30 起(
2026.1.29-beta.1)第一次出现postinstall,命令是node scripts/postinstall.js。这一段只有postinstall,持续了 4 天。 - 2026-02-04 至 2026-03-29 这 55 天是一个完全没有任何消费端钩子的窗口,共 56 个版本。
- 2026-03-31(
2026.3.31-beta.1)postinstall回来了,但命令换成了node scripts/postinstall-bundled-plugins.mjs,和第一次的postinstall.js不是同一个脚本名。 - 2026-04-15(
2026.4.15-beta.1)起preinstall才第一次加入,从此 195 个版本稳定保持preinstall+postinstall的组合,直到最新的2026.10.4。
值得注意的是 postinstall 的命令在历史上换过一次:从 node scripts/postinstall.js 改成 node scripts/postinstall-bundled-plugins.mjs。包元数据只能证明「脚本名换过」,换的动机没有公开说明,记为 UNKNOWN。
另外一个容易被忽略的细节:发布端钩子(prepare、prepack、postpack)和消费端钩子完全是两套生命周期。prepare 在 220 个版本中出现过(79.1%),prepack 在 229 个版本中出现过(82.4%),postpack 在 33 个版本中出现过(11.9%),但这些都只对从源码安装(npm install git+https://...)有意义,对已发布包不生效。而且最新的 2026.10.4 上 prepare、prepack、postpack 全部不存在——该版本共存 585 个 scripts,其中真正影响用户安装的仍然只有那两个。
三、发布身份:从个人账号到 CI,但不是一次性切换的
把 278 个版本的 _npmUser 字段(发布账号)拉出来统计,得到的是一个双发布者结构:
| 发布者 | 版本数 | 占比 |
|---|---|---|
GitHub Actions |
211 | 75.9% |
steipete(个人账号) |
67 | 24.1% |
时间线上,第一个由 CI 发布的版本是 2026.3.24-beta.1(2026-03-25T12:19:20Z),距包的首个版本 0.0.1(2026-01-29)55 天。此后 CI 逐步接管,但个人账号从未彻底退场——它又断断续续回来了 7 次。
这里有一个容易被忽略的机制细节。npm 从 2025 年起把「可信发布者」(trusted publishing)作为 OIDC 的一种形式引入,CI 发布者会带一个 trustedPublisher 字段。全量扫描的结果是:
- 211 个 CI 发布版本,100% 都带
trustedPublisher,内容为{"id":"github","oidcConfigId":"76348ccd-760...这类结构。 - 67 个个人账号发布版本,0 个带
trustedPublisher。
也就是说,字段本身就足以区分两类发布,不需要额外推断。
四、核心发现:CI 接管之后,仍有 7 个版本从个人机器直发,全部没有溯源证明
这是本次扫描最重要的结论。把「发布时间晚于首次 CI 发布」且「发布者是个人账号」两个条件取交集,得到 7 个版本:
| 版本 | 发布时间(UTC) | 消费端钩子 | 溯源证明 |
|---|---|---|---|
2026.4.5 |
2026-04-06T03:34:53 | 仅 postinstall | 无 |
2026.4.7-1 |
2026-04-08T04:24:00 | 仅 postinstall | 无 |
2026.4.8 |
2026-04-08T05:18:48 | 仅 postinstall | 无 |
2026.5.5-beta.2 |
2026-05-06T07:26:28 | preinstall + postinstall | 无 |
2026.7.1-1 |
2026-07-18T03:25:50 | preinstall + postinstall | 无 |
2026.7.1-2 |
2026-07-18T03:53:48 | preinstall + postinstall | 无 |
2026.8.1-beta.2 |
2026-08-15T05:35:31 | preinstall + postinstall | 无 |
这 7 个版本有两个共同特征,且这两个特征都不是巧合:
特征一:全部没有 SLSA 溯源证明。 211 个 CI 版本 100% 带 dist.attestations(predicateType 为 https://slsa.dev/provenance/v1),而这 7 个全部没有。这个字段不是元数据里的自我声明——我直接请求了 npm 的溯源端点做交叉验证:
| 请求的版本 | 溯源端点响应 |
|---|---|
openclaw@2026.10.4(CI 发布) |
HTTP 200,15,994 字节 |
openclaw@2026.3.28(CI 发布) |
HTTP 200,14,495 字节 |
openclaw@2026.4.8(个人直发) |
HTTP 404 |
openclaw@2026.7.1-2(个人直发) |
HTTP 404 |
CI 发布的版本能取到真实的溯源证明;个人直发的版本端点直接 404。这不是元数据的自说自话,而是第三方可独立复核的证据。
特征二:全部带上了构建机的本地目录路径。 npm 会在元数据里保留一个 _from 字段记录包的来源目录。这 7 个版本里的 6 个写的是维护者本机的绝对路径:
2026.7.1-1和2026.7.1-2的_from是file:/Users/steipete/clawdbot/.worktrees/release-2026.7.1-hotfix/.artifacts/correction-2026.7.1-1/tarballs/...——包含用户名、仓库旧名、worktree 分支名和 hotfix 目录结构。2026.4.5是file:/tmp/openclaw-npm-preflight-v2026.4.5/...;2026.8.1-beta.2是file:/tmp/openclaw-core-preflight-31865167546/...(目录名带一个数字串,像构建流水号)。
作为对照,203 个 CI 发布的版本里,_from 100% 都是归一化形式 file:preflight-tarball/openclaw-,0 个例外;而 CI 版本中另外 8 个干脆没有这个字段。所以「个人直发」和「路径未归一化」在这份数据里是同一个现象的两面。
顺带一个细节:那条路径里的 clawdbot 是该项目在 registry 上的包名变成 openclaw 之前的历史名称,全量扫描中 59 个早期版本内部仍带有这个字符串。需要说明的是,registry 的 repository 字段从 2026.2.17 起就固定为 git+https://github.com/openclaw/openclaw.git,从未变过,所以这是代码库目录名残留,不是包名或仓库地址变更。
五、这 7 个版本有一个共同点:它们从来没成为任何一个 dist-tag
如果上面那 7 个只是「CI 偶尔掉线时的兜底」,那问题就小得多。但全量核对 dist-tag 指向后,事实更微妙:
- 这 7 个版本没有任何一个曾经是
latest、extended-stable或beta标签的指向目标。 - 反过来,最近一次个人账号发布是
2026.8.1-beta.2(2026-08-15),距今 61 天。 - 而
2026.8.1-beta.3(9 天后)就回到了 CI 发布,之后连续 32 个版本由 CI 独占,直到最新的2026.10.4。
也就是说,从用户实际会装到的版本这个角度看,风险敞口比 7/278 这个比例看起来要小得多:一个坚持按 dist-tag 安装、且从不指定具体版本的普通用户,在过去 61 天里不会碰到这些直发版本。
但这个缓解不能抵消问题,原因是这些版本的元数据是公开的,且路径信息是永久可查的。任何人都可以在 registry 上读到 2026.7.1-1 的 _from 里包含维护者的 macOS 用户目录,而 registry 的版本一旦发布就不可删除。
六、证据口径:哪些是实测,哪些不能从元数据得出
本文所有数字均来自 npm registry 公开元数据的全量解析,加上对 npm 溯源端点的直接 HTTP 请求。为避免越界推断,明确区分三层:
第一层,可直接读到的字段事实(本文绝大多数结论属于此层):钩子是否存在、命令字符串原文、发布账号、dist.attestations 有无、_from 路径原文、trustedPublisher 有无。这些都是 registry 返回的原始值。
第二层,引用官方文档的机制解释:生命周期钩子的触发时机与执行顺序、provenance 的含义与限制。这些结论来自 npm 官方文档,不是我实测得到的。
第三层,明确标记为 UNKNOWN 的部分:
preinstall-package-manager-warning.mjs与postinstall-bundled-plugins.mjs两个脚本具体执行了什么行为,未实测——脚本内容不在包元数据内,我没有解包也没有运行。- 这 7 个直发版本的构建环境与 CI 是否一致(Node 版本、依赖树、测试是否通过)是否等价,未验证。包元数据里其实有个
_nodeVersion字段记录发布时的 Node 版本,但它是 npm 客户端内部字段,在 CI 发布和直发版本上都存在,只是语义未公开说明,记为 UNKNOWN。 - 这 7 个版本为何绕过 CI,原因无任何公开说明,记为 UNKNOWN。
postinstall脚本名从postinstall.js换成postinstall-bundled-plugins.mjs的动机,无公开说明,记为 UNKNOWN。prepare在 npm install 场景下「只对本地包生效」的边界条件,引用自官方文档行为描述,未在本机实测。- 注册表中的
_from、_nodeVersion等下划线前缀字段属于 npm 客户端内部字段,其长期保留策略与是否会被清理,无公开说明,记为 UNKNOWN。
七、如果你要在生产环境用 OpenClaw,这几条是可以直接执行的
上面的分析最终要落到可执行动作上。以下建议均基于本文已核实的数据,不涉及未验证的行为推断:
- 别把「有 integrity 哈希」当成供应链安全。 278 个版本全部有
dist.integrity和dist.signatures,包括那 7 个直发版本。哈希只能证明「下载的内容和发布的字节一致」,证明不了「这些字节是怎么产生的」。区分二者要看dist.attestations的有无。 - 判断一个版本是否可信,看
dist.attestations而不是看版本号大小或 beta 后缀。 判断方法:请求https://registry.npmjs.org/-/npm/v1/attestations/openclaw@,返回 200 有证明,返回 404 就是没有。本文第四节表格里的 4 次实测结果可直接作为判定方法的示范。 - 在 CI 里固定按 dist-tag 安装,不要钉死具体版本号。 理由见第五节:7 个直发版本从未成为任何 dist-tag。
- 企业环境可以考虑
--ignore-scripts。 这是 npm 官方提供的开关,会跳过生命周期钩子。但请注意,本文未实测 OpenClaw 在该开关下能否正常运行——它大概率会跳过 bundled plugins 的初始化步骤,本文记为 UNKNOWN。
八、相关阅读
- npm · life cycle scripts 与 npm install 的执行顺序——本文钩子顺序与触发时机的依据。
- npm · generating provenance statements——本文溯源证明判定方法与「provenance 不等于恶意代码豁免」的依据。
- npm · package.json 规范——
scripts字段语义。


九、本站其他相关文章与分工
为避免与站内已有内容重复,本文专门选择了此前未被开采的角度——安装期执行面与发布身份,而不是版本台账、包体积、模块面或依赖锁定。相关已有文章分工如下:
- 版本台账与
engines变更:OpenClaw 的 278 个版本台账 - 包体积与交付物形态:OpenClaw 的体积台账:LTS 线比 latest 大 51%
- 模块面与 exports 子路径:OpenClaw 的模块面:346 条子路径与 38.4% 的消失率
- 依赖锁定与
pnpm.overrides:OpenClaw 的 pnpm.overrides 被整体删除
十、来源与采集口径
- npm · package.json 规范(
scripts字段语义;2026-10-18 15:0x 采集,HTTP 200 / 186,889 字节) - npm · life cycle scripts(钩子顺序与触发时机;2026-10-18 采集,HTTP 200 / 626,137 字节)
- npm · generating provenance statements(溯源证明含义与限制;2026-10-18 采集,HTTP 200 / 562,541 字节)
- GitHub · openclaw/openclaw(
package.json原文核对;2026-10-18 采集,HTTP 200 / 942,547 字节)








