GitHub の Required Status Checks を簡単かつ安全に管理する方法
GitHub の Branch Rulesets の Require status checks to pass
を簡単かつ安全に管理するためのアプローチを紹介します。
導入
コードの品質を担保するために GitHub の Branch Rulesets の Require status checks to pass
を設定し、 Pull Request の CI が pass しないと PR をマージできないようにしましょう。
-
Require status checks to pass
を有効化 - 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 でもない場合:
---
name: test
on: pull_request
jobs:
status-check:
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions: {}
steps:
# ...
job が複数ある場合:
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: {}
---
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 するようにしましょう。
continue-on-error: true
にする
失敗を無視したい job は 失敗を無視したい job は continue-on-error: true にし、 job が失敗しても workflow は成功するようにします。
Status Check Job の実装方法
以下の手順で workflow と Branch Rulesets をセットアップします。
- pull_request workflow の job を Reusable Workflow に移す
- Reusable Workflow を pull_request workflow から呼び出す
- 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 に移します。
---
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 を書き換えます。
---
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 を追加します。
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 は
- success
- failure
- skipped
- 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