GitHub Actionsで「先の実行が終わるまで、後続を順番に待たせたい」と考えてconcurrencyを設定しても、待機中のジョブやワークフローがCancelledになることがあります。
原因は、通常のconcurrencyが無制限の待ち行列ではなく、同じグループにつき実行中1件・待機中1件を基本とする仕組みだからです。3件目が到着すると、先に待っていた2件目はキャンセルされ、3件目に置き換わります。
すべての実行を順番に処理したい場合は、cancel-in-progressだけでなく、queue: maxを設定する必要があります。これにより、同じグループ内で最大100件まで待機させられます。([GitHub Docs][1])
GitHub Actionsの待機ジョブが消える理由とconcurrencyの設定
GitHub Actionsのconcurrencyは、同じデプロイ先や共有リソースに対して、複数の処理が同時実行されるのを防ぐ機能です。
たとえば、次のような設定があるとします。
concurrency:
group: production-deploy
この設定では、production-deployという同じグループに属するワークフローは、同時に1件しか実行されません。
ただし、デフォルトのキュー設定はsingleです。そのため、実際の動きは次のようになります。
| 実行 | 状態 |
|---|---|
| 1件目 | 実行中 |
| 2件目 | 待機中 |
| 3件目が到着 | 2件目をキャンセルし、3件目が待機 |
| 4件目が到着 | 3件目をキャンセルし、4件目が待機 |
つまり、デフォルト設定は「全部を順番に処理する」のではなく、現在実行中の1件と、最新の待機1件だけを残す動作です。GitHub公式ドキュメントでも、既存のpending実行は、新しくキューに入った実行によってキャンセル・置換されると説明されています。([GitHub Docs][2])
cancel-in-progressをfalseにするだけでは解決しない
よくある誤解が、次のようにcancel-in-progress: falseを指定すれば、すべての実行が順番待ちになるというものです。
concurrency:
group: production-deploy
cancel-in-progress: false
cancel-in-progressが制御するのは、現在実行中の処理をキャンセルするかどうかです。
一方、待機中の処理を何件保持するかは、queueが制御します。
| 設定項目 | 制御する対象 |
|---|---|
cancel-in-progress | 現在実行中のジョブやワークフロー |
queue | 待機中のジョブやワークフロー |
そのため、cancel-in-progress: falseを指定しても、queueを省略した場合はデフォルトのqueue: singleが使われます。実行中の処理は継続しますが、古い待機処理は新しい処理によって置き換えられます。
すべての実行を順番に待たせる設定
後続の実行をキャンセルせず、先の実行が終了するまで順番に待機させるには、queue: maxを指定します。
concurrency:
group: production-deploy
queue: max
この設定では、同じグループについて次の状態を保持できます。
- 実行中:最大1件
- 待機中:最大100件
- キューが満杯になった後の追加実行:キャンセル
queue: maxを指定した場合、待機中の実行は、原則としてグループで待機を開始した順番に処理されます。([GitHub Docs][2])
ワークフロー全体を順番に実行する例
ワークフロー全体を直列化したい場合は、concurrencyをトップレベルに記述します。
name: Production deploy
on:
push:
branches:
- main
concurrency:
group: deploy-${{ github.workflow }}-${{ github.ref }}
queue: max
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Run deployment
run: |
echo "ここにデプロイ処理を記述"
この例では、同じワークフローかつ同じブランチから発生した実行が、1件ずつ順番に処理されます。
${{ github.workflow }}を含めることで、別のワークフローが同じブランチで動いた場合の意図しない競合を避けやすくなります。
${{ github.ref }}を含めると、mainと開発ブランチなどを別々のグループとして扱えます。すべてのブランチを共通の1列で処理したい場合は、固定名を使用します。
concurrency:
group: production-deploy
queue: max
特定のジョブだけを順番に実行する例
テストやビルドは並列実行し、デプロイだけを1件ずつ処理したい場合は、ジョブレベルにconcurrencyを設定します。
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Run tests
run: echo "テストを実行"
deploy:
needs: test
runs-on: ubuntu-latest
concurrency:
group: production-deploy
queue: max
steps:
- name: Run deployment
run: echo "デプロイを実行"
この構成では、testジョブは各ワークフローで進行できますが、deployジョブはproduction-deployグループ内で直列に処理されます。
| 設定場所 | 適しているケース |
|---|---|
| ワークフローのトップレベル | ワークフロー全体を先頭から順番に実行したい |
jobs.<job_id>.concurrency | デプロイなど、特定のジョブだけを直列化したい |
無駄なビルドやテストも含めて待たせたいならワークフローレベル、共有環境に触れる処理だけを制限したいならジョブレベルが適しています。
queue: maxとcancel-in-progress: trueは併用できない
次の設定は使用できません。
concurrency:
group: production-deploy
queue: max
cancel-in-progress: true
queue: maxは「複数の実行を待機させる」設定です。一方、cancel-in-progress: trueは「新しい実行が来たら、現在実行中の処理もキャンセルする」設定です。
この2つは動作方針が矛盾するため、同時に指定するとワークフローの検証エラーになります。queue: maxを使う場合は、通常はcancel-in-progressを省略します。([GitHub Docs][2])
目的別の設定は次のように整理できます。
| 実現したい動作 | 設定 |
|---|---|
| 実行中は継続し、最新の待機1件だけ残す | queueとcancel-in-progressを省略 |
| 実行中もキャンセルし、常に最新実行を優先する | cancel-in-progress: true |
| すべての実行を順番に処理する | queue: max |
| 最大100件を超えても絶対に失わない | concurrencyだけでは対応できない |
100件を超える可能性があり、1件も失えない処理では、外部キューや専用の実行管理を検討する必要があります。
concurrencyグループ名の衝突を確認する
queue: maxを設定しても、意図しないジョブが同じグループへ入っていると、想定外の待機やキャンセルが発生します。
特に確認したいのは、複数のワークフローファイルで同じ固定名を使用しているケースです。
concurrency:
group: production
同じリポジトリ内の別ワークフローでもgroup: productionを使っていれば、それらは同じグループとして扱われます。
ワークフロー単位で分離したい場合は、次のようにワークフロー名を含めます。
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
queue: max
反対に、複数のワークフローから同じ本番環境へデプロイしており、絶対に並列実行したくない場合は、意図的に固定のグループ名を共有します。
concurrency:
group: production-deploy
queue: max
グループ名は大文字と小文字を区別しません。たとえば、production、Production、PRODUCTIONは同じグループとして扱われます。表記だけを変えて別グループにしたつもりでも分離されないため注意が必要です。([GitHub Docs][2])
FIFOでもイベント発生順が保証されるとは限らない
queue: maxの待機処理はFIFOですが、その基準は、各ジョブやワークフローがconcurrencyグループで待機を開始した時刻です。
次の順番を絶対に保証するものではありません。
- コミットされた時刻
pushイベントが発生した時刻- 手動実行ボタンを押した時刻
- ワークフローが作成された一覧上の順番
ワークフローごとに事前ジョブや条件判定が異なる場合、後から開始されたワークフローが先にconcurrencyグループへ到達する可能性があります。
GitHub公式ドキュメントでも、FIFOはワークフローのdispatch時刻ではなく、グループで待機を開始した時刻を基準とし、実際の開始タイミングによって順序は保証されないとされています。([GitHub Docs][2])
厳密なイベント発生順で処理しなければならないシステムでは、concurrencyだけを順序保証の仕組みとして使わない方が安全です。
待機中のジョブがキャンセルされるときの確認手順
原因を切り分けるときは、次の順番で確認します。
concurrencyの設定場所を確認する
ワークフローファイル内で、concurrencyがトップレベルとジョブレベルのどちらにあるか確認します。
concurrency:
だけでなく、次の形式も検索します。
jobs:
deploy:
concurrency:
ジョブレベルの設定だった場合、ワークフロー全体ではなく、そのジョブがグループへ到達した時点から待機制御が始まります。
queueの指定を確認する
次のようにqueueが省略されていれば、待機できるのは1件だけです。
concurrency:
group: production-deploy
すべて残す必要がある場合は、次のように修正します。
concurrency:
group: production-deploy
queue: max
cancel-in-progressを確認する
次の指定があれば、待機中だけでなく、現在実行中の処理もキャンセル対象になります。
cancel-in-progress: true
順番にすべて実行したい場合は削除します。
リポジトリ内の全ワークフローを検索する
.github/workflows配下でconcurrencyやグループ名を検索します。
同じリポジトリの別ワークフローが、同じ固定グループ名を使用していないか確認してください。
.github/workflows/deploy.yml
.github/workflows/manual-deploy.yml
.github/workflows/release.yml
たとえば、これらすべてでgroup: productionを指定していれば、別ファイルであっても同じグループとして競合します。GitHub公式ドキュメントでも、複数ワークフロー間で意図しないキャンセルを防ぐには、グループ名をワークフローごとに一意にする必要があると説明されています。([GitHub Docs][1])
キュー上限を確認する
queue: maxで保持できる待機実行は最大100件です。
短時間に大量の実行が発生する構成では、キューが満杯になり、追加の実行がキャンセルされていないか確認します。
小さな検証用ワークフローで動作を確認する
本番デプロイで直接確認するのではなく、一時的な検証用ワークフローを用意すると原因を切り分けやすくなります。
name: Concurrency queue test
on:
workflow_dispatch:
concurrency:
group: concurrency-queue-test
queue: max
jobs:
wait:
runs-on: ubuntu-latest
steps:
- name: Wait
run: sleep 60
このワークフローを短時間に3回手動実行し、1件目が実行中、後続が待機状態になることを確認します。
確認後はテスト用ファイルを削除するか、通常の運用では起動しない状態にしてください。検証画面を共有するときは、組織名、非公開リポジトリ名、トークン、秘密鍵、シークレットの値などを画像やログに含めないよう注意が必要です。
GitHub Enterprise Serverを利用している場合は、GitHub.com向けの説明だけで判断せず、使用しているサーバーバージョンに対応するドキュメントでqueueの利用可否と構文を確認してください。
待機実行を残したいならqueueを明示する
GitHub Actionsの待機中ジョブがキャンセルされるのは、concurrencyのデフォルトが完全な待ち行列ではなく、実行中1件と待機中1件だけを保持するためです。
すべての実行を順番に処理したい場合は、次の形を基本にします。
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
queue: max
そのうえで、次の点を確認します。
cancel-in-progress: trueを併用しない- 別ワークフローとのグループ名衝突を確認する
- 大文字と小文字だけでグループを分けない
- 待機上限は100件と理解する
- FIFOをイベント発生時刻の絶対順序と考えない
まずは対象のYAMLでqueueが省略されていないかを確認し、複数の待機実行を残す必要がある場合はqueue: maxへ変更してください。
[1]: https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency “Control the concurrency of workflows and jobs – GitHub Docs”
[2]: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax “Workflow syntax for GitHub Actions – GitHub Docs”

コメント