🐧

GitHub の Required Status Checks を簡単かつ安全に管理する方法

2025/03/16に公開

GitHub の Branch Rulesets の Require status checks to pass を簡単かつ安全に管理するためのアプローチを紹介します。

導入

コードの品質を担保するために GitHub の Branch Rulesets の Require status checks to pass を設定し、 Pull Request の CI が pass しないと PR をマージできないようにしましょう。

  1. Require status checks to pass を有効化
  2. job を Status checks that are required に追加

このとき全部の job を Status checks that are required に追加すると、 job を追加・削除・リネームするたびに Branch Rulesets を修正しないといけなくなります。
これは面倒ですし、 Branch Rulesets の更新が漏れると job が失敗しているのに PR がマージできるようになってしまいます。

そこで、これをより簡単かつ漏れなく安全に管理する方法を紹介します。

Status checks that are required に追加する job を Required Job と呼ぶことにします。

まとめ

  • pull_request workflow ごとに Status checks that are required に job を 1 つ追加
    • workflow ごとに Required Job の名前は変える
  • pull_request workflow はなるべく 1 つにまとめる
    • ファイルを分割したい場合、 Reusable Workflow にする
    • 例外的に actionlint を実行する workflow は独立させる
      • workflow が壊れると actionlint が実行されないので、 actionlint の workflow は独立させて壊れにくくする
  • Workflow の Path filter on.<push|pull_request|pull_request_target>.<paths|paths-ignore> の代わりに dorny/paths-filter を使う
  • pull_request workflow file に追加する job は 1 つか 2 つにする
    • それ以上の job は Reusable Workflow にする

job が 1 つしかないかつ Reusable Workflow でもない場合:

.github/workflows/test.yaml
---
name: test
on: pull_request
jobs:
  status-check:
    runs-on: ubuntu-24.04
    timeout-minutes: 10
    permissions: {}
    steps:
      # ...

job が複数ある場合:

.github/workflows/test.yaml
jobs:
  status-check:
    runs-on: ubuntu-24.04
    if: failure()
    timeout-minutes: 10
    permissions: {}
    needs:
      - test
    steps:
      - run: exit 1
  test:
    uses: ./.github/workflows/workflow_call_test.yaml
    permissions: {}
    # secrets: {}
.github/workflows/workflow_call_test.yaml
---
name: test (workflow_call)
on: workflow_call
jobs:
  foo:
    # ...
  bar:
    # ...

Status checks that are required に追加する check は workflow につき 1 つにする

全部の job を Status checks that are required に追加するのではなく、 全部の job が成功したときだけ成功する check を一つだけ追加します。
そうすることで Status checks that are required を逐一更新する必要がなくなり、漏れる心配もなくなります。
この Job を Status Check Job と呼ぶことにします。

Status Check Job を如何に実装するかは後述します。

pull_request workflow ごとに Required Job の名前は変える

pull_request workflow ごとに Required Job の名前は変えましょう。
同じ名前にすると、ある workflow でその job がまだ実行されてなくても PR がマージされる可能性があります。
job が一つしかないような場合でも、何らかの理由で job の起動が遅れる可能性があるので、やはり名前は変えたほうが良いでしょう。

pull_request workflow は極力 1 つにまとめる

workflow を追加や削除するたびに Status checks that are required を更新する必要があって面倒ですし、更新が漏れると workflow が失敗しているのに PR がマージできてしまうので、 workflow は極力一つにまとめましょう。

Workflow はマージしたいけどファイルは分割したい場合、 Reusable Workflow にしてファイルを分割できます。

Workflow の Path filter の代わりに dorny/paths-filter を使う

Path filter on.<push|pull_request|pull_request_target>.<paths|paths-ignore> を設定している Workflow の job を Required Job にすると、 Workflow が skip されたさいに status check が pass せずに PR をマージできなくなります。
よって workflow の job を Required Job にできなくなるため、 job が失敗しても PR をマージできてしまいます。
そこで、 Workflow の Path filter の代わりに dorny/paths-filter のような action を使って job level で skip するようにしましょう。

失敗を無視したい job は continue-on-error: true にする

失敗を無視したい job は continue-on-error: true にし、 job が失敗しても workflow は成功するようにします。

Status Check Job の実装方法

以下の手順で workflow と Branch Rulesets をセットアップします。

  1. pull_request workflow の job を Reusable Workflow に移す
  2. Reusable Workflow を pull_request workflow から呼び出す
  3. status check job を pull_request workflow に追加

ちょっと分かりにくいかもしれませんが、要は「全ての Job が成功しているか否か」を、全ての Job を Reusable Workflow に移してその Workflow が成功しているか否かで判定しているところが肝です。
Workflow を Status checks that are required に追加するようなことは出来ないので(それが出来ればこんなトリッキーなことはしなくて済むんですが)、 Workflow の結果によって結果が変わる Job を定義して、その Job を Status checks that are required に追加しています。

1. pull_request workflow の job を Reusable Workflow に移す

pull_request workflow を test.yaml とします。
workflow_call workflow workflow_call_test.yaml を追加します。
名前を test (workflow_call) とします (名前が被らないようにしましょう)。
test.yaml の job を workflow_call_test.yaml に移します。

.github/workflows/workflow_call_test.yaml
---
name: test (workflow_call)
on: workflow_call
jobs:
  foo:
    # ...
  bar:
    # ...

2. Reusable Workflow を pull_request workflow から呼び出す

workflow_call_test.yaml を test.yaml から呼び出すように test.yaml の jobs を書き換えます。

.github/workflows/test.yaml
---
name: test
on: pull_request
jobs:
  test:
    uses: ./.github/workflows/workflow_call_test.yaml

secret を使っていたり permissions の設定が必要な場合は適宜設定してください。

  test:
    uses: ./.github/workflows/workflow_call_test.yaml
    permissions:
      contents: read
    secrets:
      APP_ID: ${{secrets.APP_ID}}

3. status check job を pull_request workflow に追加

test job を status check に追加することは出来ません。
そこで test が成功したら skip し、失敗したら失敗する job status-check を追加します。

.github/workflows/test.yaml
jobs:
  test:
    uses: ./.github/workflows/workflow_call_test.yaml
  status-check:
    runs-on: ubuntu-24.04
    if: failure()
    timeout-minutes: 10
    permissions: {}
    needs:
      - test
    steps:
      - run: exit 1

job の status は

  1. success
  2. failure
  3. skipped
  4. cancelled

の 4 種類です。

test の結果によって status-check の結果がどうなるか、そして PR がマージできるか整理します。

status of test status of status-check mergeable
success skipped true
failure failure false
skipped skipped true
cancelled cancelled false

見ての通り、 test job の結果通りに PR のマージ可否が決まっています。

job が一個しかないような場合

複数の job をまとめるのに workflow_call は有用ですが、 job が一個しかないような場合は分けなくても良いでしょう。
ごく普通のパターンです。

jobs:
  test:
    runs-on: ubuntu-24.04
    steps:
      # ...

このアプローチの懸念点

このアプローチの懸念点は、事情を知らない人が workflow_call ではなく pull_request workflow に job を追加してしまう可能性があることです。
そうすると、その job が失敗しても PR をマージできてしまいます。
大人数で開発しているような場合、この問題は起こりやすいでしょう。
逆に個人で開発している OSS とかであれば自分さえわかっていれば間違える可能性は低いでしょう。

とりあえずコードコメントを書いておきましょう。

jobs:
  # Don't add jobs to this file. Please add jobs to workflow_call_test.yaml.
  test:
    uses: ./.github/workflows/workflow_call_test.yaml
  status-check:
    runs-on: ubuntu-24.04
    if: failure()
    timeout-minutes: 10
    permissions: {}
    needs:
      - test
    steps:
      - run: exit 1
  # Don't add jobs to this file. Please add jobs to workflow_call_test.yaml.

CI でバリデーションしようと思えばできますが、若干面倒です。

Discussion