23 万个 runner 里 30% 已经过期——9 月 25 号 GitHub 不再往过期 runner 派活,而且它是静默失败的
9 月 16 号这天,如果你的 CI 早上开始变得”怪怪的”——workflow 没报错、日志也没有红字,但 job 就是一直挂在 Queued 不动——大概率不是你的代码有问题,是你的 self-hosted runner 版本过期了。
GitHub 已经把话说死:9 月 25 日起,永久停止往”过期版本”的 self-hosted runner 派发任务。而在那之前,9 月 14、16、18 号是最后几场 brownout(演练),过期 runner 会间歇性拒绝注册、拒绝执行 job。今天就是其中一天。
最坑的地方在于:它是静默失败。workflow 层面看不到任何 error,runner 日志里只有一行 Runner version v2.xxx.0 is deprecated and cannot receive messages,然后 job 就永远躺在队列里。你不主动去查,根本不知道。
为什么会这样
这不是 GitHub 想收钱,是底层真的不兼容了。Actions 后端做了重构,旧平台大概一天处理 2300 万个 job,新平台能扛 1.2 亿以上,企业场景每分钟能启动的 job 数是以前的 7 倍。旧的 runner 版本缺了新架构需要的通信协议和安全层,物理上聊不到一起。
规则其实有两条,很多人只看到第一条就以为搞定了:
- 注册门槛:runner 版本必须 ≥ 2.329.0,否则连注册 / 重新注册都过不了。
- 执行时效:每次有新版本发布,runner 必须在 30 天内更新,否则停止接收 job。
关键就是第二条。GitHub 的原话很直白:”2.329.0 只是接入新平台、能收到更新的最低门槛,它不是一劳永逸的运行版本。”你把镜像钉死在 2.329.0,它能注册成功,然后悄无声息地不再接活。
谁真的会中招
绝大多数 runner 默认是自动更新的,只要你没关,大概率已经没事了。真正危险的是这几类”主动关掉更新”的:
- 启动参数带了
--disableupdate的——常见于几个月前写好的 Docker 镜像、Helm chart、Terraform 配置,这个 flag 通常埋在很深的地方,写完就忘了。 - 容器化 runner(K8s / ARC)——Actions Runner Controller 默认就是关闭自动更新的,你 Helm values 里那个镜像 tag,本质上就是一个”未知日期的定时停产”。RunnerDeployment / AutoscalingRunnerSet 里的
runnerVersion字段得手动 bump。 - 气隙(air-gapped)环境——够不到 GitHub 的更新服务,就算开了自动更新也一样掉队。
怎么快速摸清自己有几台要中招
GitHub 在 9 月 3 号放了一个接口,输入版本号就能查它的 EOL 时间:
gh api orgs/<org>/actions/runners/deprecations/2.334.0
# {
# "runner_version": "2.334.0",
# "runtime_deprecates_at": "2026-08-10T00:00:00Z",
# "registration_deprecates_at": "..."
# }
两个实测的坑:
- 文档写”发布后 30 天”,但接口实际返回的是 63–71 天左右。如果你按发布日期自己算,两个方向都会算错。
- runner 列表接口现在带 version 字段了,不需要在每台机器上装 agent 就能盘点整支舰队。
有人把这两个接口封成了一个 gh 扩展 gh-runner-eol,最有用的是 --scan:它会去 grep 你的 Dockerfile、ARC Helm values、RUNNER_VERSION= 变量、下载 URL 里钉死的版本,再拿去 API 上比对。这抓的正是那些”还没被 scale up、但迟早要出问题”的 runner——光看在线 runner 的 dashboard 是看不见它们的。
gh extension install canblmz1/gh-runner-eol
gh runner-eol audit --scan .
# OVERDUE 18 runners v2.334.0 runtime support ended 2026-08-10 26 days overdue
# WARNING 12 runners v2.336.0 runtime support ends 2026-09-15 10 days left
# OK 13 runners v2.337.0 no end-of-life scheduled
注意一个权限坑:workflow 里的 GITHUB_TOKEN 读不了 self-hosted runners,会直接 403。要用带 Self-hosted runners: read(org 级)或 Administration: read(repo 级)的 fine-grained PAT / GitHub App token。实在不行就用 mode: scan 只扫源码里的钉死版本。
你现在该做的三件事
- 先扫描,别先动手。把在线的 + 源码里钉死的 runner 版本都拉出来,对照 API 算出各自的 EOL 日期,排个优先级。
- 升级要连”源头”一起改。镜像 tag、Helm values、Terraform、
--disableupdate的启动脚本,全都得动。只重启一堆现成的旧 runner 是没用的。 - 给钉死版本加一道 CI 卡口。用带 SARIF 输出的方式接进 CI,让某次 PR 里新钉进的过期版本直接变成 Code Scanning 的告警,超期就 exit 非零,挂到每周定时任务上,出事前先被叫醒。
GHES(自建 GitHub Enterprise Server)这次不受影响;GHEC 带 Data Residency 的版本 7 月 31 号就已经全量执行了。如果你在 Cloud 上,把 9 月 25 日记进日历——那不是”提醒”,是”截止”。
评论区
登录后可评论。