🗃️

GitHub Actionsで大量差分時にワークフローが起動しない問題と解決策

に公開

GitHub Actionsは大量差分をトリガーしない

GitHub Actionsでは pushpull_request などのイベントを指定し、その発生時にワークフローを実行できます。

さらに paths を設定すると、特定のファイルやディレクトリが変更された場合のみワークフローを動作させることも可能です。

しかし、稀にこのトリガーが意図通りに動作しないケースがあります。

これはGitHub Actionsの仕様として、検知できるファイルの変更数が最大300ファイルまでに制限されているためです。

(参考: GitHub Discussions

検証

例えば、以下のように dir-a 用のデプロイ用ワークフローを作成します。

name: Directory A Continuous Deployment Workflow

on:
  push:
    paths:
      - 'dir-a/**'
      - '.github/workflows/dir-a-cd.yml'
    branches:
      - 'develop'
      - 'staging'
      - 'production'

jobs:
  hello-world:
    runs-on: ubuntu-latest
    steps:
      - name: Say Hello
        run: echo "Hello, world!"

これを dir-adir-e まで計5つ用意し、各ディレクトリで100個の空ファイルを作成します。

touch {1..100}.txt

合計500ファイルの変更を加えて、develop ブランチへ変更を反映すると、全てのワークフローの実行が期待されますが、3つのワークフローしか実行されないことが確認できます。

回避方法

この制限を回避する方法のひとつが dorny/paths-filter の利用です。

※ このアクションは公式提供ではありません。利用は自己判断でお願いします。

仕組みとしては、まず「変更検知専用のワークフロー」を1つ用意し、そこで paths-filter で変更有無を判定します。

その結果に応じて、必要なワークフローを workflow_call で呼び出します。

変更検知専用のワークフローの例
name: Multi Directory Continuous Deployment Workflow

on:
  push:
    paths:
      - 'dir-a/**'
      - '.github/workflows/dir-a-cd.yml'
      - 'dir-b/**'
      - '.github/workflows/dir-b-cd.yml'
      - 'dir-c/**'
      - '.github/workflows/dir-c-cd.yml'
      - 'dir-d/**'
      - '.github/workflows/dir-d-cd.yml'
      - 'dir-e/**'
      - '.github/workflows/dir-e-cd.yml'
    branches:
      - "develop"
      - "staging"
      - "production"

jobs:
  changes:
    runs-on: ubuntu-latest
    outputs:
      dir-a: ${{ steps.filter.outputs.dir-a }}
      dir-b: ${{ steps.filter.outputs.dir-b }}
      dir-c: ${{ steps.filter.outputs.dir-c }}
      dir-d: ${{ steps.filter.outputs.dir-d }}
      dir-e: ${{ steps.filter.outputs.dir-e }}
    steps:
    - uses: actions/checkout@v4
    - uses: dorny/paths-filter@v3
      id: filter
      with:
        base: ${{ github.ref }}
        filters: |
          dir-a:
            - 'dir-a/**'
            - '.github/workflows/dir-a-cd.yml'
          dir-b:
            - 'dir-b/**'
            - '.github/workflows/dir-b-cd.yml'
          dir-c:
            - 'dir-c/**'
            - '.github/workflows/dir-c-cd.yml'
          dir-d:
            - 'dir-d/**'
            - '.github/workflows/dir-d-cd.yml'
          dir-e:
            - 'dir-e/**'
            - '.github/workflows/dir-e-cd.yml'

  call-dir-a-workflow:
    needs:
      - changes
    if: ${{ needs.changes.outputs.dir-a == 'true'}}
    uses: ./.github/workflows/dir-a-cd.yml
    secrets: inherit

  call-dir-b-workflow:
    needs:
      - changes
    if: ${{ needs.changes.outputs.dir-b == 'true'}}
    uses: ./.github/workflows/dir-b-cd.yml
    secrets: inherit

  call-dir-c-workflow:
    needs:
      - changes
    if: ${{ needs.changes.outputs.dir-c == 'true'}}
    uses: ./.github/workflows/dir-c-cd.yml
    secrets: inherit

  call-dir-d-workflow:
    needs:
      - changes
    if: ${{ needs.changes.outputs.dir-d == 'true'}}
    uses: ./.github/workflows/dir-d-cd.yml
    secrets: inherit

  call-dir-e-workflow:
    needs:
      - changes
    if: ${{ needs.changes.outputs.dir-e == 'true'}}
    uses: ./.github/workflows/dir-e-cd.yml
    secrets: inherit

これにより、中央の検知ワークフローが実行され、変更ファイル数に関係なく必要なワークフローだけが動くようになります。

注意点

理想は大量のファイル変更が発生しないよう運用を工夫すべきですが、どうしても避けられないケースでは今回紹介した方法が有効です。

ただし、通常時は paths による実行制御のみで十分であり、実行時間やコストの面でも有利です。

むやみに集約ワークフローを導入すると、逆に全体の実行時間が延びる可能性があるため注意しましょう。

Discussion