GitHub Actions は最初は「YAML を書けば動く」感覚で始められますが、本格的に活用しようとすると次のような壁にぶつかりがちです。
- push と pull_request の両方で CI が走って二重実行される
- PR では取得できた context が dispatch では null になる
- schedule 実行時だけ条件式が成立しない
- デプロイをどのトリガーで行うべきか定まらない
こうした問題に対して場当たり的にif:を増やしたり、workflow を分割して対処しようとすると、規模が大きくなり破綻する原因となります。
私がこの課題に取り組んで理解したのは、次の点が重要だということです。
トリガー(on)が変わると、workflow 内で扱える context(変数)が変わる
この関係を理解できると、条件式が整理され、workflow がシンプルになり動作も一気に安定します。
この記事では、実務で頻繁に使う5つのトリガーに絞り「発火条件」「context」「実務での使いどころ」「よくある事故」を整理します。
この記事で扱う5つのトリガー
GitHub Actions には多くのトリガーがありますが、実務で頻繁に登場するものは限られています。
この記事では、網羅的なリファレンスではなく「現場で遭遇するものに絞って深掘りする」ことを目的として、次の5つを扱います。
push
pull_request
issue_comment
workflow_dispatch
schedule
ここから先のセクションでは、
- そのトリガーが発火する条件
- どの
contextがどのように変化するのか
という構成で、5種類それぞれを解説していきます。
① push
発火条件
pushは、ブランチまたはタグにコミットが到達したタイミングで発火します。
典型的な設定は次のようなものです。
on:
push:
branches:
- main
- develop
paths:
- src/**
発火タイミングを整理すると以下の通りです。
| 状況 | 発火有無 |
|---|---|
| ローカルから main に push | 発火 |
| develop への push | 発火 |
| main へのマージ(PR merge) | 発火 |
| タグ作成 | 発火 |
| draft PR の状態で push | 発火(pull_request ではなく push 扱いのため) |
| リベースして force push | 発火 |
pathsの対象外ファイルのみ変更して push |
発火しない(paths フィルタに一致しないため) |
| fast-forward merge で main に新しい commit が載らなかった | 発火しない(main にコミット到達が発生していないため) |
push は CI の中心になる一方で、「PR merge でも push が走る」という点が pull_request と混同されやすいため、期待した動作をしないことがあります。
context の中身
pushのcontextの特徴を端的に言うと「コミットおよびブランチの情報が中心で、PR 情報は含まれない」 です。
代表的なcontext値は次の通りです。
| フィールド | 値の例 |
|---|---|
github.event_name |
"push" |
github.ref |
"refs/heads/main" |
github.ref_type |
"branch" または "tag"
|
github.sha |
commit hash |
github.event.head_commit.message |
コミットメッセージ |
github.actor |
実行ユーザー |
PRとは違いgithub.event.pull_request.*は存在しません。
そのため、push で動かす Jobs に PR 固有の情報を期待するとnullになり落ちます。
② pull_request
発火条件
pull_requestは、PR のライフサイクルに応じて様々なタイミングで発火します。
代表的なtypesは以下の通りです。
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
主要イベント(types)の動作を整理すると次のようになります。
| イベント | 発火タイミング |
|---|---|
| opened | PR 作成時 |
| synchronize | PR に対する追加 push(コミット追加) |
| reopened | PR 再オープン時 |
| ready_for_review | Draft → Ready に変更時 |
| converted_to_draft | Ready → Draft に変更時 |
| closed | PR がクローズしたとき(マージ含む) |
| merged |
closedの一種だが github.event.pull_request.merged が true |
補足として、レビューコメント・ラベル追加・アサインなどではpull_requestは発火しません。
レビュー操作を契機にする場合はpull_request_targetまたはissue_commentを組み合わせます。
(補足)push との違い
pull_request では github.ref は head ブランチではなく refs/pull/<番号>/merge になることがある。
例:
- PR の実体のブランチ: feature/foo
- github.ref: refs/pull/123/merge
これは GitHub が自動生成する「マージコミット用の仮ブランチ」です。
これを知らずにgithub.refでブランチ名を判定しようとすると事故が起きます。
context の中身
pull_request は PR のメタデータ・差分情報を含んだ最も情報量の多いイベントです。
利用頻度の高い context を抜粋します。
| フィールド | 値の内容 |
|---|---|
github.event_name |
"pull_request" |
github.event.pull_request.number |
PR 番号 |
github.event.pull_request.title |
PR タイトル |
github.event.pull_request.user.login |
作成者 |
github.event.pull_request.head.ref |
PR 元ブランチ名(例: "feature/foo") |
github.event.pull_request.base.ref |
マージ先ブランチ名(例: "main") |
github.event.pull_request.merged |
マージ済みかどうか(true/false) |
github.event.pull_request.draft |
Draft かどうか |
github.event.pull_request.labels |
ラベル一覧 |
③ issue_comment
発火条件
issue_commentは Issue・PR に対するコメントが投稿されたときに発火します。
名前的に「Issue 限定」と誤解されがちですが PR も対象です。
on:
issue_comment:
types: [created]
typesを指定しない場合、コメントの作成・編集・削除すべてで発火します。
context の中身
issue_comment の context は、pull_request や push とは全く異なる構造です。
特に重要な仕様として、github.event.issue.numberは「コメントした対象」の番号が入ります。
つまり、
- Issue にコメント → Issue番号
- PR にコメント → PR番号
となります。
| フィールド | 値の内容 |
|---|---|
github.event_name |
"issue_comment" |
github.event.comment.body |
コメント本文 |
github.event.issue.number |
コメント対象の Issue/PR 番号 |
github.event.issue.pull_request |
対象が PR の場合にのみ存在 |
github.event.comment.user.login |
コメント投稿者 |
github.actor |
実行ユーザー(コメント投稿者になる場合が多い) |
重要なのはgithub.event.issue.pull_requestの有無で
そのコメントが PR なのか Issue なのか判別できるという点です。
if: github.event.issue.pull_request != null
④ workflow_dispatch
発火条件
workflow_dispatchは手動実行用のトリガーです。
GitHub UI や API を通して、ユーザーが任意のタイミングでworkflowを実行できます。
最もシンプルな宣言は次の通りです。
on:
workflow_dispatch:
実務では inputs を定義してパラメータ実行する形がよく使われます。
on:
workflow_dispatch:
inputs:
environment:
type: choice
description: "Deploy target environment"
options:
- dev
- stg
- prod
required: true
workflow_dispatchはシンプルな手動実行トリガーですが、本当の強みはinputsと組み合わせた選択式・パラメータ実行にあります。
context の中身
代表的な context は以下の通りです。
| フィールド | 値の内容 |
|---|---|
github.event_name |
"workflow_dispatch" |
github.event.inputs |
手動入力された値 |
github.ref |
実行対象に選んだブランチ |
github.sha |
実行対象ブランチの最新コミット |
github.actor |
実行ユーザー |
つまり、dispatch では push や pull_request と違い「どのコードに対して実行したいか」をユーザーが選択します。
⑤ schedule
発火条件
scheduleは cron 形式で 定期実行を行うトリガーです。
最も基本的な例は次の通りです。
on:
schedule:
- cron: "0 9 * * *" # 毎朝9時 (UTC)
scheduleの実行は GitHub の負荷状況により 最大10分程度遅延する可能性があることが明記されています。
厳密な時刻保証は必要ない前提で設計すべきです。
context の中身
schedule の context は独特です。特に押さえるべき点は次の2つです。
-
github.refは常にデフォルトブランチ(例: refs/heads/main)になる - PR などの context は存在しない
代表的な context 値は次の通りです。
| フィールド | 値の内容 |
|---|---|
github.event_name |
"schedule" |
github.ref |
refs/heads/main(固定) |
github.sha |
デフォルトブランチの最新コミット |
github.actor |
"github-actions"(ほぼ固定) |
つまり schedule 実行は「main の最新コードに対して定期的に処理を行う」という仕様が標準です。
ここを理解していないとif:判定が期待通りに動かず、事故が発生します。
実務での設計パターン(レシピ集)
ここまで「トリガーごとの発火条件と context の中身」を見てきましたが、
実際の CI 設計では、この理解をベースに目的に応じて最適なトリガーを選ぶことが重要になります。
現場でよく使われる設計パターンを、最小構成のレシピ形式でまとめます。
パターン1:push は「変更検知用」、pull_request は「レビュー品質保証用」
push と pull_request を両方書くとどちらでも CI が走るため、役割分担が必須です。
push → コード変更を検知してビルドや静的解析を走らせる
pull_request → レビューでの品質確認に必要なテストのみ走らせる
メリット:
- 二重実行や無駄な負荷が消える
- PR のレビュー速度が上がる
- PR のためのテストと本番想定のテストを分けられる
パターン2:デプロイは push ではなく workflow_dispatch に集約
開発初期は push での自動デプロイが便利ですが、チーム規模が大きくなるにつれ事故が起きやすくなります。
再現性・安全性を考えると、次のような整理が効果的です。
push/pull_request → CI(品質担保)
workflow_dispatch → CD(リリース)
メリット:
- 誰が、いつ、どこにデプロイしたかが明確になる
- デプロイの承認フローや環境指定がしやすい
- ロールバックも対称設計で実装できる
パターン3:issue_comment でデプロイ承認・再実行フローを作る
コメントベースの運用を導入すると「レビュー → コメント → 実行」という流れが自然に定着します。
例:
/deploy stg
/deploy prod
terraform apply
terraform deploy
メリット:
- 人の意思が入るべき処理を push や PR に寄せずに済む
- PR の場で完結する
- 権限や対象環境の扱いが柔軟
特に「PR レビュー者の一言でデプロイ可能」にしたい場合に適しています。
パターン4:schedule は「早期検知タスク」専用
schedule を push/PR と同じ扱いにするとCIが重くなり破綻します。
スケジュール実行用に軽めのタスクだけを切り出すのが現場で安定するパターンです。
例:
- 毎朝ユニットテスト(外部依存の変化検知)
- モノレポの依存パッケージ更新
- CI 自己診断(期限切れ、キャッシュ肥大、stale ブランチ検出など)
ポイント:
- main の最新コミットで実行されるため、PR テストとは役割が違う
- 成果物がスパムにならないよう通知抑制設計が有効
まとめ
最適な CI/CD の鍵は 「どのトリガーで何をすべきか」 を明確に役割分担することです。
| 検知 | 検証 | 意思決定 | 定期 |
|---|---|---|---|
| push | pull_request | workflow_dispatch / issue_comment | schedule |
「on を理解すると if が楽になる」
GitHub Actions の条件分岐はif:を使うのが基本ですが、
if:の書き方に悩む原因の多くは on の理解不足によって context が想定と違っていることです。
逆に言えば、
「どのトリガーのとき、どんな context が渡ってくるのか」
を理解できると、if:は驚くほどシンプルになります。
まず押さえるべき原則
if は「発火条件の補正」
on は「発火そのものを定義」
onが適切なら、if:はほとんど必要ありません。
誤解の典型例は以下のような書き方です。
on: push
jobs:
build:
if: github.event_name == 'push'
これは冗長です。onの段階で push に限定できているため if:は不要です。
使うべき if になるのは「on だけでは判断できない分岐」
具体例をトリガー別に整理します。
| トリガー | if が必要になる代表例 |
|---|---|
| push | ブランチによって振り分けたい場合 |
| pull_request | Draft かどうか、ラベル有無、PR タイトルなどで判定 |
| issue_comment | コメント本文や投稿者で分岐 |
| workflow_dispatch | inputs による振り分け |
| schedule | 定期実行時だけ軽量ジョブにしたい、などの分岐 |
シンプルで強力な条件式の例
PR がマージされた場合だけ実行する
if: github.event_name == 'pull_request' && github.event.pull_request.merged == true
コメントで/deployと書かれた場合だけ実行する
if: github.event_name == 'issue_comment' && contains(github.event.comment.body, '/deploy')
手動実行でprodを指定した場合だけ実行する
if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod'
main ブランチへ push された場合だけ実行する
if: github.ref == 'refs/heads/main'
よくある事故と回避策まとめ
GitHub Actions で発生するトラブルの多くは、YAML 構文ミスでもジョブ設定ミスでもなく、「トリガーと context の誤解」から生まれます。
ここでは実務で特によく遭遇する事故を、原因と対策と合わせて整理します。
| 事故内容 | 原因 | 回避策 |
|---|---|---|
| CIが二重実行される | push と pull_request 両方でCIを走らせている | トリガーの役割分担、if: github.event_nameで補正 |
| PR merge で意図せずデプロイが走る | merge → push の流れを考慮していない | デプロイは dispatch/コメントに集約 |
| PR番号を取得しているつもりで Issue番号になっている | issue_comment の context を誤解 |
github.event.issue.pull_request != null判定でPRのみ処理 |
| schedule 実行時に条件式が成立せず何も動かない |
github.refが常に main になる仕様を未理解 |
if: github.event_name == 'schedule'を併用 |
| forkからのPRだけ落ちる | secrets が渡らない仕様 | 権限が必要な処理は pull_request_target or コメント承認 |
| デプロイの再実行で過去のコミットを参照してしまう | dispatch の ref 指定が曖昧 | 入力でブランチ選択 or ref: refs/heads/main明記 |
| コメントのたびに実行されて暴走 |
/deployなどの判定なし |
contains(comment.body, '/deploy')などの条件必須 |
まとめ
GitHub Actions は、YAML の書き方やテクニックより先に「on × context」を理解することで扱いやすくなります。
- push/pull_request/dispatch/comment/schedule の役割を決める
- context の違いを前提に設計する
- on で切り、if で補正する
この3点が揃うと、条件式の爆発、workflow の増殖、予期せぬ実行などのトラブルが減り、運用が落ち着きます。