返回博客

Monorepo 同一次 push 触发两条 CI,Web 白跑一遍的根因与修法

项目是一个 Monorepo,同时产出两类构建产物:一个 Web 前端(部署到 CDN),一个桌面客户端(Tauri / Electron 打包 dmg/exe)。为了分别控制发布节奏,建了两条 GitHub Actions 工作流:deploy-web.yml 由分支推送触发,desktop-build.yml 由 tag 触发。

某次只是给桌面端做版本号 bump,推送 commit + tag。打开 Actions 看到:Web 的 CI 也在跑,而且跑完了,再看 deploy 日志——Web bundle 内容和上一次完全一样,这次根本没有任何前端源码被改动。

现象

git commit -m "chore: bump desktop to v0.1.13"
git push                           # → deploy-web.yml 开始运行(浪费)
git tag desktop-v0.1.13
git push origin desktop-v0.1.13   # → desktop-build.yml 开始运行(正常)

Actions 面板里同一个 commit SHA 上同时出现两个绿色进度圈。Web 这条从头到尾 rebuild + deploy,耗时 3 分钟,输出和上一次毫无二致。

根因

push.branches: [main] 这个触发条件对 所有 推送到 main 的 commit 无差别触发,不管这次 commit 改了什么文件。桌面版本 bump 的 commit 只动了 desktop/src-tauri/tauri.conf.jsonupdate.json 和几个桌面专属脚本,和 Web 的 src/public/ 一个字节都没碰,但 GitHub Actions 不知道这些——它只认触发条件,不看 diff。

坑:branch-triggered workflow 对所有 commit 无差别触发

push.branches: [main] 只是一个 ref 过滤器,不是 path 过滤器。只要有 commit 推到 main,不管改了哪些文件,都会触发。Monorepo 里多产物共享同一条主干时,这个默认行为会造成大量 CI 浪费——改桌面跑 Web build,改文档跑 Web build,改发布脚本跑 Web build。

正解

deploy-web.yml(即 branch-triggered 那条)加 paths-ignore,把属于另一个产物域的所有文件列进去:

# .github/workflows/deploy-web.yml
on:
  push:
    branches: [main]
    paths-ignore:
      # 桌面端源码树
      - 'desktop/**'
      # 桌面端专属 CI 工作流(改它不应该重新部署 Web)
      - '.github/workflows/desktop-build.yml'
      # 桌面端专属发布脚本
      - 'scripts/upload-desktop-installers.mjs'
      - 'scripts/upload-update-json.mjs'
      - 'scripts/build-update-json.sh'
      # 桌面端生成的 release manifest
      - 'update.json'
      # 纯文档,不影响任何构建
      - '**.md'
  workflow_dispatch:   # ← 必须保留,用于手动强制重跑

paths-ignore 的核心判断原则:

| 放进去 | 不能放进去 | |--------|-----------| | 另一产物域的源码树 desktop/** | 共享源码 src/**lib/** | | 另一产物域的 CI 文件 | 当前工作流自身文件(要能立即验证 CI 配置修改) | | 另一产物域的发布脚本 | package.json、lockfile(影响所有产物) | | 生成的 release manifest | | | **.md | |

正解:paths-ignore 做产物域隔离,workflow_dispatch 做手动逃生门

只在 branch-triggered 工作流上加 paths-ignore,tag-triggered 那条不用动——tag 本来就只在你主动创建 tag 时才触发,不存在误触发问题。同时一定要保留 workflow_dispatch:,这样当你确实需要在桌面 bump 之后强制重跑 Web deploy(比如改了环境变量),可以用 gh workflow run deploy-web.yml 手动触发,而不需要 push 一个假的 commit。

排查:如果已经在跑,先取消

发现时两条流水线都在运行,先取消那条浪费的:

# 列出最近几条运行,找到要取消的 ID
gh run list --limit 5 --json databaseId,workflowName,headSha,status
 
# 取消
gh run cancel <DATABASE_ID>

然后在 follow-up commit 里加上 paths-ignore,之后就不会再误触发。

一句话外卖

Monorepo 多产物共主干时,branch-triggered CI 默认是无差别广播,paths-ignore 是给它装上产物域过滤器的最小代价方案——但别忘了同时留一个 workflow_dispatch 出口。