用 GitHub Actions 大半年了,从最简单的 push 触发构建,到现在管着三个项目的定时部署、日志收集、自动同步。
中间踩了不少坑。挑 7 个值得说的,每个都带着我当时怎么踩的、后来怎么解的。
1. cron 不准时,有时延迟十几分钟
这是最早碰到的。我设了一个每天 UTC 16 的定时任务,本以为北京时间 00 准时跑,结果有时候 00 才开始,最夸张一次延迟了 12 分钟。
GitHub Actions 的 schedule 走的是 GitHub 内部队列,高峰期(北美工作时间)延迟更明显。官方文档说了不保证准时。
如果你的任务对时间敏感,别纯靠 cron。加一个外部触发,比如 Cloudflare Workers 的 Cron Trigger 调 GitHub 的 workflow_dispatch API,或者用 UptimeRobot 定时 ping 一个 webhook。我自己的博客定时构建用纯 cron 就够了,晚几分钟不影响正确性,但工作项目里换成了外部触发。
2. 缓存配置不对,白跑两分钟
刚开始用 actions/setup-node@v4 时没配 cache,每次 CI 都从头装依赖,node_modules 巨大的项目要装 90 秒。
加上 cache: pnpm(或 npm/yarn)后降到 15 秒。但这里有个坑,缓存 key 要带上 lockfile 的 hash,否则缓存命中的是旧依赖。
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpmsetup-node 的 cache 参数会自动用 lockfile 生成 key,不用手动写。但如果你用了 monorepo 或者自定义的 lockfile 路径,得手动指定 cache-dependency-path。
3. GITHUB_TOKEN 默认权限不够 push
默认情况下,GITHUB_TOKEN 只有读权限。想 push 代码到仓库,得去 Settings → Actions → General → Workflow permissions 改成 Read and write permissions。
这个坑我在做定时构建时踩的。workflow 跑了一半,commit 成功了,push 报 403。日志里看到的错误信息其实挺清楚,但第一次遇到时还是查了一会儿。
改完权限后,还要注意 permissions: contents: write 要在 workflow 文件里显式声明。只改设置不写 permissions 字段,有时候还是不行。
4. 并发构建互相覆盖
同一个仓库,push 了两次,两个 workflow 同时跑,同时往 gh-pages 推送。后一个把前一个覆盖了。
解法,用 concurrency 字段。
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: falsecancel-in-progress: false 让后触发的排队等待,不会取消前一个。如果你不在乎前一个被取消(比如纯测试任务),设成 true 可以省 Actions 额度。
5. Secrets 里的值有特殊字符
往 Secrets 里存一个 API Key,值里带 + 号。在 workflow 里用的时候没转义,curl 命令报错。
GitHub Secrets 不会做任何转义,原样注入。如果你的值里有特殊字符,在 workflow 里用引号包起来,
curl -H "Authorization: Bearer '${{ secrets.API_KEY }}'"或者用 environment 变量传递,避免直接内联,
env:
API_KEY: ${{ secrets.API_KEY }}
run: curl -H "Authorization: Bearer $API_KEY"后者更安全,也不会在错误日志里泄露。
6. workflow_dispatch 的 inputs 不做校验
手动触发 workflow 时可以传参数,但 inputs 不做类型校验。你声明 type: choice,传一个不在 options 里的值也能跑。
如果你的 workflow 依赖 input 值做分支判断,最好在 step 里加一层校验,
if [[ ! "$DEPLOY_ENV" =~ ^(dev|staging|prod)$ ]]; then
echo "Invalid environment: $DEPLOY_ENV"
exit 1
fi7. checkout 默认浅克隆,git log 取不到历史
actions/checkout@v4 默认 fetch-depth: 1,只拉最新一个 commit。如果你需要在 CI 里比较文件变更、读 git log,得改成 0:
- uses: actions/checkout@v4
with:
fetch-depth: 0我的定时构建脚本需要读上一次的日志状态来判断今天新上线了哪些文章,浅克隆取不到历史 commit,改成 0 后正常。
这 7 个坑里,1、3、4、7 是影响功能的,不解决 workflow 跑不通。2、5、6 是影响效率和安全性的,不解决能跑但迟早出事。
GitHub Actions 本身不难,文档分散在几十个页面里,有些行为只有踩了才知道。