GitHub Actions 已经从警告阶段进入实际破坏阶段:在 GitHub 托管运行器上,Node 20 不再是 JavaScript Actions 可用的运行时。自 2026 年 9 月 23 日起,这些 Actions 使用 Node 24 运行;此前允许仓库继续使用 Node 20 的临时退出机制也已被移除。

科技编辑插图:开源项目维护者正在检查 GitHub Actions 从 Node 20 迁移到 Node 24,画面包含 CI 构建产物和自托管 runner。

对大多数仓库而言,表面上的修复很简单:更新 Action 引用,然后继续工作。但对开源维护者来说,真正需要处理的范围更广。一个 Action 在源代码树中看起来可能已经更新,已发布的标签却仍然指向旧的 dist 构建包。工作流也可能已经使用最新的 JavaScript Action,却仍在自托管运行器、旧 Mac 或 ARM32 开发板上失败。本地模拟 Actions 的工具还可能继续执行错误的运行时,制造兼容性良好的假象。

更实际的做法,是把这次变化视为一次发布流程与支持矩阵审计,而不是简单地把 node20 替换成 node24。运行时切换是眼前发生的事件;更持久的问题在于:开源项目如何说明受支持的运行器、如何发布不可变的 Action 构建产物,以及如何测试用户真正运行的环境。

9 月 23 日发生了什么

GitHub 的退役通知称,运行器现在使用 Node 24 执行 JavaScript Actions。同时,ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION 已不可用。这个变量只是迁移期间的临时逃生通道,现在不再是受支持的兼容机制。GitHub.com 与启用数据驻留的 GitHub 都会应用这一变化。

必须区分 Action 的运行时与工作流中安装的 Node 版本。JavaScript Action 会在 action.yml 中声明执行运行时,通常类似下面这样:

runs:
  using: node24
  main: dist/index.js

这个设置决定 GitHub 用哪个 Node 运行时启动 Action。它与后续使用 actions/setup-node 的步骤是分开的;后者为仓库自己的构建、测试或打包命令安装 Node。即使通过 setup-node 安装了 Node 20,也不会让一个声明 node20 的 Action 在已移除 Node 20 Action 运行时的运行器上重新获得兼容性。反过来,修改 Action 声明的运行时,也不会自动改变项目测试所使用的 Node 版本。

因此,一个仓库可能同时存在多个彼此独立的迁移面:

  • 仓库自己的 action.yml 文件中声明的 JavaScript Actions。
  • 工作流文件引用的第三方 Actions。
  • dist/ 中随 Git 提交的打包 JavaScript。
  • 自托管运行器的版本与操作系统。
  • 项目用于测试、构建、CLI 或发布脚本的 Node 版本。

GitHub 早先的弃用通知已经给维护者留出了过渡时间,并公布了最终移除日期。Node.js 自身也记录了 Node 20 已于 2026 年 3 月 24 日结束生命周期。因此,Actions 的变化移除了一个本来就建立在上游不再支持的运行时之上的执行路径;这不是 Node.js 新发布政策的突然变化。

第一次审计:找出真正会执行的内容

先从工作流文件开始,但不要停在那里。既要搜索直接使用的 Action,也要搜索 Action 的定义。仓库中的第一轮检查可以覆盖:

.github/workflows/
action.yml
action.yaml
*/action.yml
*/action.yaml

在工作流文件中,寻找类似 uses: owner/project@vN 的引用。主版本标签并不能告诉你被引用的发布版本使用哪个 Node 运行时。必须检查解析后的标签或提交。对于规模较小的社区 Action,这一点尤其重要:它可能已经更新了源代码,却没有发布新的版本。

如果 Action 由同一个仓库维护,检查 action.yml 并确认 runs.using。如果值为 node20,应改成 node24;当项目要求提交 dist/ 时,还要重新构建打包输出。许多 JavaScript Actions 执行的是编译文件,而不是仓库中的 TypeScript 或 JavaScript 源码。只修改源代码、留下旧构建包,并不算完整发布。

接下来要确认依赖是否支持 Node 24。Node 24 不只是元数据解析器接受的一个标签;它带来更新的 V8 和一组更新的 Node API,可能暴露模块加载、包导出、OpenSSL、文件系统或流 API 方面的假设。风险并不是每个 Node 20 Action 都一定失败,而是项目可能从未在新运行时下实际执行过用户安装的发布产物。

对于第三方 Actions,优先选择维护者明确记录 Node 24 支持的发布版本。不要因为 @v4 比 @v3 数字更大,就假定把引用改成 @v4 一定正确。阅读发布说明,检查 Action 元数据,并确认新版本是否同时引入无关的行为变化。运行时迁移不应成为跳过输入、输出、权限和安全行为审查、直接接受主版本变化的理由。

官方 Action 的更新为何值得参考

GitHub 的官方 Actions 提供了这次迁移的具体样本。当前 actions/checkout 的变更日志记录了 Node 24 相关更新,当前 actions/setup-node 文档也说明了使用 Node 24 的较新 Action 版本。这些项目并非只修改一个元数据字段:它们发布新版本、更新文档、运行自己的测试矩阵,并说明运行器要求及行为变化。

小型仓库可以借鉴这种模式。一份负责任的迁移版本应该明确回答四个问题:哪个 Action 版本包含这项变化;要求什么运行器版本;哪些操作系统或架构不再支持;Action 的输入或输出是否变化。即使代码差异很小,这些答案也应写进发布说明。

actions/setup-node 项目还提醒我们,运行时迁移可能与其他变化重叠。当前文档描述了基于 Node 24 的 Action,并提供了包管理器缓存的相关指引。文档警告,在具有提升权限或包含敏感凭据的工作流中,自动缓存并不总是合适。这个警告并非由 Node 24 引起,但它属于同一次审计,因为维护者往往会同时更新 Action 版本和工作流安全设置。

用户为了获得 Node 24 支持而升级 Action 时,可能同时得到缓存默认值、身份验证行为、受支持运行器版本或包管理器检测方面的变化。因此,正确的升级审查问题不是“YAML 能否解析”,而是“现在有哪些代码以哪些权限、带着什么缓存状态执行”。

自托管运行器是最容易出问题的地方

GitHub 的退役通知指出了 Node 24 的两个兼容性限制:macOS 13.4 及更早版本不兼容,ARM32 也不在官方支持范围内。这些限制对开源项目尤其重要,因为开源项目的贡献者和用户环境通常比默认的 GitHub 托管运行器矩阵更复杂。项目可能在 Ubuntu 上构建通过,但用户却在旧 Intel Mac、树莓派级别的 ARM32 设备,或防火墙后的私有运行器上运行 Action。

当工作流使用 macos-latest 这样的宽泛标签时,操作系统问题很容易被忽略。GitHub 托管镜像会随时间移动,而自托管标签通常只描述一台需要管理员手动升级的机器。支持自托管运行器的仓库,应当记录最低运行器版本和支持的操作系统范围,而不应把 GitHub 托管镜像当成完整的支持政策。

架构问题更加隐蔽。ARM32 设备在托管 CI 中并不常见,因此默认矩阵可能从未覆盖它们。如果项目用户会在本地或小型边缘设备上运行 Actions,维护者必须决定是否仍支持 ARM32,并明确写出决定。“Action 可以在 Linux 上运行”还不够精确,因为不同平台的运行时架构支持可能不同。

自托管管理员在诊断 Action 失败前,应先更新 Actions 运行器软件。某些兼容 Node 24 的 Action 版本要求足够新的运行器,具体最低版本应以 Action 的发布文档为准。更新运行器并不能让不兼容的操作系统变得兼容,但旧运行器可能在 Action 代码真正启动之前就给出误导性错误。

稳妥的顺序是:分阶段升级运行器;在移除密钥或替换为测试凭据的条件下运行有代表性的工作流;随后测试涉及部署、发布或发行权限的工作流。只有基本单元测试通过还不够,尤其当 Action 还会上传构建产物、签名包、创建拉取请求或交换 OIDC 令牌时。

已发布的构建产物也是软件的一部分

JavaScript Actions 往往会把编译后的 dist 目录提交到 Git,这样用户在工作流运行期间就不需要安装依赖或构建 Action。这会形成一个发布陷阱:仓库里的源文件已经现代化,但用户消费的标签仍然包含旧的打包文件。

维护者应检查发布引用中 Action 实际会执行的那个构建产物,至少确认以下事项:

  1. action.yml 或 action.yaml 声明的是 node24。
  2. main 或 pre 路径指向预期的生成文件。
  3. 生成文件存在于已发布的标签中。
  4. 发布版本确实由预期的提交构建。
  5. 发布说明指出用户应选择的标签或提交。
  6. 测试工作流执行的是打包后的构建产物,而不是通过开发捷径只测试源代码。

使用发布机器人或构建工作流的项目,还应检查该工作流本身是否依赖旧 Action。迁移可能以循环方式失败:维护者更新了 Action,但发布工作流仍调用过时的 checkout、setup、打包或发布 Action。应先升级用于生成构建产物的 CI,再依赖这套 CI 证明产物已经更新。

这里同样要重视固定引用。可变的主版本标签对用户很方便,但安全敏感型工作流可能会固定到完整提交 SHA,并通过审查流程更新。如果项目同时发布面向人的标签和带签名或摘要固定的引用,发布说明应解释二者的关系。运行时迁移是清理模糊引用、说明某个引用为何可信的好时机。

支持 Node 24 不等于项目所有内容都应运行在 Node 24 上

包含 Action 的仓库有两个不同的兼容性问题。第一,Action 本身能否由 GitHub 的 Node 24 运行时启动。第二,项目自己的应用、库、测试套件或命令行工具是否支持 Node 24。两者可能采用不同的支持政策。

一个 Action 可以在 Node 24 上运行,同时调用仍支持 Node 18、20 和 22 的项目。Action 的运行时是自动化平台的实现细节,而测试软件所用的运行时可能是项目对外公开的兼容性承诺。维护者不应仅仅因为 GitHub 改变了 Action 运行时,就悄悄提高项目的最低 Node 版本。

反过来,如果 Action 实现使用了 Node 24 专有 API,就应在贡献者文档中说明。否则,本地运行测试的开发者可能使用 Node 20,直到打包后的 Action 在 GitHub 上执行时才失败。一个小型运行时矩阵可以避免这种错位:

strategy:
  matrix:
    node: [22, 24]
steps:
  - uses: actions/checkout@v7
  - uses: actions/setup-node@v7
    with:
      node-version: ${{ matrix.node }}
  - run: npm ci
  - run: npm test

这只是示例,并非适用于所有项目的建议。版本应符合项目声明的支持范围。如果 Action 本身要作为打包产物测试,工作流还应通过用户实际使用的相同路径调用它,而不是只导入源模块。

发布 npm 包的项目也可以借此检查包元数据。engines.node 应描述应用或库支持的范围,不应简单复制 Action 运行时。应提交锁文件,并把依赖更新与运行时迁移分开审查,以便失败时能够判断具体原因。

本地模拟器可能掩盖问题

在本地运行 GitHub Actions 工作流的工具很有用,但它们不会自动复现 GitHub 托管运行器的行为。本地模拟器可能选择带 Node 16 或 Node 20 的容器镜像,可能没有实现相同的 Action 运行时映射,也可能使用不同的运行器版本。因此,本地运行通过,并不能证明发布后的 Action 能在 GitHub 的 Node 24 运行时上工作。

actions/checkout 项目的一个问题说明了这种混淆:本地执行工具可能报告旧容器镜像,即使 Action 发布版本已经使用 Node 24。问题未必在 Action 本身,也可能在模拟器的运行时模型。维护者应记录哪些工作流部分已在本地验证,哪些部分必须依赖真实的 GitHub 托管运行器或正确配置的自托管运行器。

实用的测试计划应有意结合两种环境。先在本地运行快速单元测试和集成测试,再针对 Action 的打包发布版本运行最小化的真实工作流。真实工作流应覆盖重要输入,例如私有仓库、大文件、代理设置、凭据、拉取请求事件或生成的输出,具体取决于 Action 的用途。本地测试仍然适合快速迭代,但不应被描述为平台级验证的替代品。

用户应如何修改自己的工作流

用户通常不需要重写每一个工作流步骤。首要任务是把 Action 引用更新到支持 Node 24 的版本,然后检查剩余错误。应先处理对工作流最核心的 Actions:checkout、setup-node、缓存、构建产物上传与下载、语言环境设置、容器操作以及发布 Actions。

一个稳妥的升级清单包括:

  • 阅读 Action 发布说明,确认支持 Node 24。
  • 检查 Action 文档记录的最低运行器版本。
  • 审查权限、密钥、缓存设置与身份验证变化。
  • 分别测试来自 fork 的拉取请求和内部 push。
  • 在实际支持的每个操作系统与架构上测试工作流。
  • 明确保留项目自己的 node-version 或版本文件。
  • 使用试运行或暂存目标重新执行发布与推送任务。
  • 移除过时的 Node 20 退出变量;它们已不再提供回退路径。

不要把 actions/setup-node 与 uses: Action 使用的运行时混为一谈。下面的工作流为 shell 命令安装 Node 24:

- uses: actions/setup-node@v7
  with:
    node-version: 24
- run: npm test

它无法修复另一个元数据仍写着 using: node20 的第三方 Action。那个 Action 必须由维护者更新,或者替换为仍在维护的替代品。如果 Action 已被遗弃,而项目无法接受运行未经审查代码的风险,那么一个小型本地脚本可能比采用未经验证的 fork 更安全;但这个决定必须结合 Action 的权限和数据流来判断。

用户也应避免一次性修改所有 Action 版本。在同一个提交中同时更新 checkout、setup-node、缓存、构建产物处理和部署 Action,会让失败很难定位。分阶段提交一系列小型拉取请求,可以留下更清晰的审计轨迹,也便于回滚。对社区项目而言,这种清晰度还方便下游打包者和维护旧分支的贡献者。

发布说明中应写什么

“迁移到 Node 24”这样的发布说明总比沉默好,但用户需要操作层面的细节。说明应指出变化影响的是 Action 运行时、项目运行时,还是两者;应写明第一个包含该变化的版本,并说明用户是否需要修改工作流引用。

说明还应列出已知限制。例如,GitHub 通知明确指出,macOS 13.4 及更早版本以及 ARM32 不再支持基于 Node 24 的 JavaScript Actions。如果项目还有独立限制,例如原生依赖、最低运行器版本或较新 npm 的要求,也应写在同一份发布说明中。

维护者应说明如何验证迁移。一条用于检查 Action 元数据的简短命令、CI 矩阵链接,以及“已测试打包后的 dist 构建产物”这样的声明,比罗列一长串依赖升级更有用。如果某个稳定分支不会接收这次迁移,也应直接说明,并提供最后一个受支持的 Action 版本。

这也是发布支持政策的好时机。开源用户经常根据公开 CI 中“碰巧通过”的环境推断支持范围。一张表或一段文字,把 GitHub 托管运行器、自托管运行器、本地模拟器、操作系统与架构分开说明,可以避免这种无意形成的承诺。用户可以在部署流水线成为边界探测器之前,判断升级是否适合自己的环境。

安全与供应链影响

Node 20 退役是一次兼容性事件,但升级 Action 同时也是供应链变化。Action 在工作流上下文中运行,可能接触仓库内容、令牌、云凭据、签名材料或部署系统。任何带有这些权限的新版本,都应像其他依赖一样接受审查。

不要只看发布标题,还要比较旧引用与新引用之间的差异。检查权限、shell 命令、网络端点、构建产物处理和任务结束后的清理行为。对于处理不受信任拉取请求的 Action,要确认新版本是否改变了代码检出时机,或凭据何时变得可用。运行时迁移不能成为跳过这些检查的理由。

缓存尤其值得关注。缓存可以提高速度,但如果工作流在处理不受信任输入前恢复依赖数据,可能形成缓存投毒或凭据暴露路径。当前 setup-node 文档建议,在不需要自动包管理器缓存、且工作流拥有提升权限或处理敏感信息时,关闭该缓存。这个建议并不专属于 Node 24,但当 Action 升级导致缓存行为变化时,它就直接相关。

把 Action 引用固定到经过审查的提交 SHA,可以降低标签被意外移动的风险,但会增加维护成本。使用 Dependabot、Renovate 或其他更新机制的项目,应确认工具理解这次运行时迁移,不会反复重新打开同一个过时主版本。签名发布、来源证明元数据和可复现构建都是有用补充,但都不能替代对将以密钥权限运行的代码进行审查。

Action 尚未迁移时的替代方案

对于未维护的 Action,没有适用于所有项目的统一替代方案。正确选择取决于它的功能、所需权限,以及它的行为对项目是否不可或缺。通常有几种路径。

第一,若项目仍然活跃且迁移并不复杂,可以请求维护者发布 Node 24 版本,或直接贡献迁移。拉取请求应包含构建产物、在受支持运行器矩阵上的测试,以及发布文档。只更新 runs.using 而不测试打包文件,并不完整。

第二,替换为维护状态良好、发布流程和支持政策清楚的项目。比较时要看行为和权限,而不只是功能名称。一个星标较少但维护透明、权限模型更窄的 Action,可能比一个流行却发布流程不透明的 Action 更合适。

第三,把一小段逻辑移入普通工作流步骤或仓库脚本。这可以减少对 Action 运行时的依赖,但不会自动让代码更安全。shell 脚本仍然拥有工作流权限,必须验证输入、正确引用数据,并避免暴露密钥。

第四,在准备迁移期间,把旧 Action 隔离到专用任务或仓库中。这是遏制方案,不是永久解决方案。应限制其权限,避免传入部署凭据,并明确声明其输出。项目还应说明这是临时安排,避免下游用户把它误认为正式支持。

当原项目已经废弃、代码又足够小且可审计时,fork 可能是合理选择,但它也会带来长期维护债务。fork 应说明与上游 Action 的关系,保留许可证声明,发布自己的版本,并解释用户如何验证生成的构建产物。只有改了名字的标签、没有发布流程,只是把不确定性转移了位置。

对开源自动化更大的启示

平台管理的运行时提醒我们,一个开源 Action 同时面对两份维护契约。第一份来自源代码生态:依赖、编译器、包管理器和语言版本。第二份来自自动化平台:运行器版本、支持的操作系统、执行语义、权限和构建产物约定。项目可能履行了其中一份,却违反另一份。

Node 24 迁移还暴露了开源基础设施中反复出现的弱点:用户经常通过一次失败的任务,才发现项目的支持政策。其实没有必要这样。仓库可以发布经过测试的矩阵,保持发布产物可见,声明最低运行器版本,并增加定时任务,在平台强制要求之前测试即将到来的运行时变化。

Action 运行时应被当作产品表面来测试。它需要发布说明、兼容性测试、安全审查和回滚方案。用户安装的并不只是源代码;元数据、生成的构建包、标签、运行器和权限共同构成了真正的软件包。

对用户而言,眼前的任务是更新受支持的 Action 版本,并验证真正重要的环境。对维护者而言,更重要的任务是让下一次迁移变得平淡无奇。这意味着发布支持政策、明确的运行器要求、可复现的发布产物,以及测试用户实际执行路径的 CI。Node 24 已成为 GitHub Actions 上的新基线。迁移质量的衡量标准,不只是 YAML 文件是否发生变化,而是贡献者能否在它触及生产环境之前理解新的边界。

来源与延伸阅读