Windows 上 npx 报 ECOMPROMISED,检查 npm 缓存锁的心跳更新
npm 11.6.2 的缓存锁定时检查可能因慢 stat 重叠,误报 Lock compromised。核对真正的 Node/npm 组合,使用串行心跳实现,保留并发锁保护。
Windows 的 CI 在运行 npx 时退出,错误是 ECOMPROMISED: Lock compromised,堆栈落到 libnpmexec/lib/with-lock.js。先检查 npm 的缓存锁实现与文件系统调用耗时,不要把它直接当成包内容被篡改,也不要立即删除整个 npm 缓存。
明确出现过问题的组合包括 npm 11.6.2、Node.js 24.10.0 与 Windows Server 2025。另一组 Windows 环境在 Node 25.2.0 上也有相同日志。Node 22 的一次成功对照实际运行的是 npm 10,后来使用同类 npm 实现时同样出现问题,因此不能把“换成 Node 22”写成确定修复。
依赖锁文件与这里的缓存锁不同
错误路径通常在 npm cache 的 _npx 子目录,涉及 concurrency.lock。这是保护并发缓存操作的目录锁,与项目的 package-lock.json 用途不同。重新生成依赖锁文件,不能修正它的心跳调度。
在失败的同一 CI 步骤或终端确认实际组合:
node --version
npm --version
npm config get cache
再保存首条 ECOMPROMISED 堆栈,核对是否确实经过上述 with-lock 模块。另一个进程修改或移走了锁、磁盘访问失败,也可能使锁被判为失效;错误码本身不能证明就是下面的定时器重叠。
npm 11.6.2 每秒用 setInterval 启动一次锁检查。某次异步 stat 如果长时间没有完成,后续间隔仍会发起新检查。多个检查交错读取旧 mtime、更新新 mtime,恢复时就可能把自己造成的变化判成锁已被别人替换。延迟日志中还出现过二十多秒的 stat;这说明该环境发生过慢调用,不能据此推断所有 Windows 文件系统都有相同耗时或同一个底层原因。
更新的是实际执行 npx 的 npm
已核对的正式 npm 11.6.3 改为在一次更新完成后,再用 setTimeout 安排下一次检查,避免这些心跳相互重叠;锁过期阈值也由 5 秒变为 60 秒。正式 12.2.1 仍保留串行更新方式。选择版本时同时遵守当前 Node 和项目的支持范围,不只更换 CI 里显示的 Node 大版本。
例如 npm 12.2.1 声明的 Node 范围是 ^22.22.2 || ^24.15.0 || >=26.0.0。旧 Node 24.10.0 不在这份范围内,不能强制安装后忽略 engines 提示。可以按现有工具链管理流程,选择受支持、包含该修正的完整 Node/npm 组合,并重新确认实际调用到哪一份 npm。
CI 用安装 action、工具缓存或镜像提供 npm 时,在真正失败的步骤里查看版本;交互终端更新成功,不会自动替换镜像中的旧 npx。修改前保存工作流、镜像标签和版本锁定,避免排错时引入另一套依赖。
暂时需要降低缓存争用时,可以给独立 CI 作业使用各自受控的缓存路径;这有助于隔离并发进程,却不保证修复旧实现内部的重叠心跳。不要通过关掉锁检查、吞掉 AbortSignal 或删除仍由其他任务持有的锁继续执行。
用原来受信任的包与版本复查同一工作流,确认命令完成且没有新的 ECOMPROMISED,再观察是否仍有异常慢的文件调用。若升级后依然失败,应保留新堆栈继续查磁盘、权限与真正的并发写入,不能无限重试把偶然成功当成锁已经可靠。工具可能已经执行过部分操作,重跑有副作用的 CLI 前还须核对其实际结果。


