GitHub Actions 的定时任务晚了六个小时才跑,是时区没配对还是在排队?

北京时间早上 9 点,换成 UTC 是凌晨 1 点。GitHub Actions 的 schedule 默认按 UTC 解释 cron,写的时候忘了换算,任务会准时地差出整整 8 个钟头,分钟数一点不乱。要是拖了六个多小时,而且每次晚多少都不一样,那就不像时区问题,更像是在排队。

时区可以直接写进配置

GitHub 的触发事件文档里写着,cron 默认按 UTC 跑,也可以填一个 IANA 时区字符串。写法是在 cron 下面加一个 timezone 键,文档的例子是纽约时间工作日早上 5:30:

on:
  schedule:
    - cron: '30 5 * * 1-5'
      timezone: "America/New_York"

换成国内时间,填 Asia/Shanghai 这个 IANA 名称就行,省得自己减 8。文档还提到,时区如果有夏令时,春季拨快那一天,落在被跳过那个小时里的任务会顺延到下一个有效时间,像 2:30 会改到 3:00 执行。国内不用夏令时,这条主要是给配了海外时区的人看的。

另外几条语法限制:cron 是 POSIX 的五段格式,最短间隔 5 分钟,@daily、@hourly 这类写法不支持。

想要北京时间每天 9:17 跑,可以这么写:

on:
  schedule:
    - cron: '17 9 * * *'
      timezone: "Asia/Shanghai"

已经按 UTC 换算过的旧 cron,加上 timezone 时记得把钟点改回本地时间,不然等于减了两回,任务会提前 8 个钟头跑。

一个 workflow 可以挂好几条 schedule,触发本次运行的是哪一条,能从 github.event.schedule 里读到,它的值就是那串 cron。

晚跑和不跑

文档对延迟说得很坦白:Actions 整体负载高时 schedule 事件可能被推迟,每个整点开头都是高峰。压力再大一些,排队的任务还会有被直接丢掉的风险。官方建议是把执行时间挪到每小时里别的分钟。所以 0 * * * * 这种写法最容易挤,改成 17 分、43 分这样的零碎分钟会好一些,但文档只说能降低延迟的概率,没承诺准点。

想确认是不是排队,可以在任务第一步把实际时间和触发它的 cron 打进日志,跑上一阵再对账:

      - run: |
          echo "cron: ${{ github.event.schedule }}"
          date -u +"%Y-%m-%dT%H:%M:%SZ"

几次记录里,如果始终差 8 小时,回头检查时区配置。要是忽大忽小,cron 又正好卡在整点,多半是平台那边在排队。

任务干脆没跑的话,再对一下这几点:

  • workflow 文件要放在默认分支才会触发,定时任务也只在那里跑,用的是它最新的提交。在功能分支上改了 cron 不会生效。
  • 公开仓库 60 天没有活动,定时任务会被自动停用,得按文档手动重新启用。有写权限的人提交一次修改 cron 的改动,也能让它重新激活。
  • 定时任务的通知只发给最后一个改过 cron 的人,别人收不到提醒很正常。

既然排队的任务可能被丢,脚本本身最好能补课:每次运行别只处理「上一个周期」,而是从上回成功的位置接着做,中间漏掉一轮,后面也能把活补齐。上次跑到哪里,可以记在仓库外的存储里,也可以从产物或者接口数据里推出来。

真要准点怎么办

文档没给准点保证,对时间敏感的任务就别全押在 schedule 上。可以让外部的调度器到点调 API,触发 workflow_dispatch 或 repository_dispatch,不过这两个事件也都要求 workflow 文件在默认分支上。或者干脆在自己的机器上用 cron 或 systemd timer 跑,Actions 只负责构建和发布。

你们的定时任务一般会晚多久,碰到过被直接丢掉的情况吗?