GitHubの必須チェックがPendingのままになる原因と直し方|Actions・ルールセット対策

GitHub Actionsで「テスト対象のコードを変更していないのに、必須チェックがExpectedやPendingのままでマージできない」という問題は、テストの成否ではなく、必須チェックの結果自体がGitHubへ報告されていないことが原因です。

特に注意したいのが、pathspaths-ignorebranches、コミットメッセージなどによってワークフロー全体が省略されたケースです。ワークフローが起動しなければ、必須チェックは成功にもスキップにもならず、結果待ちのまま残ります。

一方、ワークフローを起動したうえでジョブを条件付きスキップした場合、そのジョブは成功として扱われます。したがって、必須チェックを単純に解除するのではなく、対象となるすべてのプルリクエストで必須チェックが何らかの結果を返す設計に直すことが基本的な解決策です。([GitHub Docs][1])

目次

GitHubの必須チェックがExpected・Pendingのままでマージできない理由

ブランチ保護やルールセットで必須チェックを設定すると、GitHubは対象コミットに対して、そのチェックが完了したことを確認してからマージを許可します。

たとえば、ルールセットでtestというチェックを必須にしている場合、テストを実行する必要がない変更であっても、GitHub側には次のどちらかの結果が必要です。

  • テストを実行して成功した
  • テスト不要と判断され、スキップまたは成功として完了した

ワークフローそのものが起動しなかった場合は、どちらの結果も作成されません。そのため、GitHubは「まだtestの結果が報告されていない」と判断し、マージを止めます。

表示上は、次のような文言になることがあります。

  • Expected
  • Pending
  • Waiting for status to be reported
  • 「ステータスが報告されるのを待機中」

ステータスの定義では、expectedはチェック結果の報告待ちを表します。一方、pendingは同時実行制限などによる待機状態にも使われます。表示だけで原因を決めつけず、対象コミットに対応するワークフロー実行が存在するかを確認することが重要です。([GitHub Docs][2])

ワークフロー全体の省略とジョブのスキップは結果が異なる

今回の問題を理解するうえで最も重要なのが、ワークフロー単位の省略とジョブ単位のスキップを区別することです。

条件の設定場所条件に一致しない場合必須チェックへの影響
on.pull_request.pathsワークフロー全体が起動しない結果が報告されず、待機状態が続く
on.pull_request.branchesワークフロー全体が起動しない結果が報告されず、待機状態が続く
コミットメッセージによるスキップワークフロー全体が起動しない結果が報告されず、待機状態が続く
jobs.<job_id>.ifワークフローは起動し、対象ジョブがスキップされるスキップされたジョブは成功として扱われる
steps[*].ifジョブは起動し、対象ステップだけ省略されるジョブ全体として完了結果を返せる

GitHub公式ドキュメントでも、パス、ブランチ、コミットメッセージによってワークフロー全体が省略された場合、関連するチェックがPendingのまま残り、マージを妨げると説明されています。

一方、条件式によってジョブがスキップされた場合、そのジョブはSuccessとして報告され、必須チェックであっても通常はマージを妨げません。([GitHub Docs][1])

最初に確認するべき4つの項目

設定を変更する前に、どの必須チェックが、どのコミットに対して待機しているのかを特定します。

必須チェックの正確な名前

ブランチ保護やルールセットに登録されているチェック名と、プルリクエストに表示されるチェック名を照合します。

似た名前ではなく、文字列として同じかを確認してください。

たとえば、次の名前は別のチェックとして扱われる可能性があります。

test
tests
CI / test
ci-test
test (Node.js 22)

ジョブ名を変更したにもかかわらず、ルールセットに古いチェック名が残っていると、GitHubは存在しなくなったチェックの報告を待ち続けます。

チェックの提供元

必須チェックには、特定のGitHub Appを提供元として指定できる場合があります。

たとえば、同じbuildという名前でも、次の結果は提供元が異なります。

  • GitHub Actionsが作成したチェック
  • 外部CIサービスが作成したチェック
  • 独自のGitHub Appが作成したチェック

期待されているGitHub Appとは別のサービスが同名のチェックを成功させても、必須要件を満たさないことがあります。

また、Checks APIによるチェックとCommit Status APIによるステータスが同じ名前で存在する場合、両方の通過が必要になることがあります。チェック名だけでなく、提供元と種類も確認してください。([GitHub Docs][1])

対象となっている最新コミットSHA

必須チェックは、プルリクエストの過去のコミットではなく、原則として最新コミットSHAに対して成功している必要があります。

たとえば、次の状態では要件を満たしません。

  1. コミットAでテストが成功する
  2. その後、コミットBを追加する
  3. コミットBではワークフローが起動しない
  4. コミットAの成功結果だけが残っている

画面上に過去の緑色のチェックが見えていても、現在の最新コミットに対する結果でなければ、必須チェックは完了しません。([GitHub Docs][1])

head commitとtest merge commitのどちらが対象か

プルリクエストでは、ブランチ先端のコミットだけでなく、ベースブランチとの仮マージ結果に対してチェックが実行されることがあります。

プルリクエストのチェック欄に次のような表示がある場合は、対象となるコミットを確認してください。

Showing checks for the merge commit

テスト用マージコミットにステータスが存在する場合は、そのコミットの結果が判定に使われます。存在しない場合は、プルリクエストの最新head commitが使われます。([GitHub Docs][1])

ワークフローが省略された原因を確認する

必須チェックが報告されていない場合は、.github/workflows内のYAMLファイルを確認します。

pathsとpaths-ignore

次の設定では、srcまたはtests配下に変更がある場合だけワークフローが起動します。

on:
  pull_request:
    paths:
      - 'src/**'
      - 'tests/**'

READMEやドキュメントだけを変更したプルリクエストでは、ワークフロー自体が起動しません。

このワークフロー内のジョブを必須チェックに指定していると、GitHubには成功もスキップも報告されず、待機状態が続きます。

paths-ignoreも同様です。

on:
  pull_request:
    paths-ignore:
      - 'docs/**'

変更されたすべてのファイルがdocs/**に一致した場合、ワークフローは起動しません。

また、branchespathsを同時に指定している場合は、両方の条件を満たしたときだけワークフローが実行されます。どちらか一方だけ一致しても起動しない点に注意してください。([GitHub Docs][3])

branchesとbranches-ignore

次の設定では、mainをマージ先とするプルリクエストだけが対象です。

on:
  pull_request:
    branches:
      - main

実際のマージ先がdevelopreleaseであれば、このワークフローは起動しません。

ルールセットが複数ブランチに適用されているのに、ワークフロー側ではmainだけを対象にしていると、ブランチによって必須チェックが永遠に報告されない状態が発生します。

コミットメッセージのスキップ指定

pushまたはpull_requestで起動するワークフローは、コミットメッセージに特定の文字列が含まれていると省略されます。

代表的な指定は次のとおりです。

[skip ci]

[ci skip]

[no ci]

[skip actions]

[actions skip]

コミットメッセージ末尾のトレーラーとして、次の指定が使われている場合も対象です。

skip-checks: true

プルリクエストでは、HEADコミットのメッセージを確認します。

コミットメッセージによって必須ワークフローが省略された場合は、スキップ指定を含まない新しいコミットをプルリクエストへ追加し、最新コミットに対してチェックを発生させる必要があります。([GitHub Docs][4])

手動実行したワークフローの結果

Actions画面からworkflow_dispatchで手動実行し、同じブランチでテストを成功させても、ルールセットの必須チェックとして認識されない場合があります。

GitHub Actionsのジョブによるチェックがプルリクエストの必須チェックとして評価されるには、次のような対応イベントから起動されている必要があります。

  • push
  • pull_request
  • pull_request_review
  • pull_request_target
  • deployment
  • deployment_status

workflow_dispatchでプルリクエストのhead branchを指定して実行しても、そのチェックはプルリクエストのチェック欄に表示されず、ルールセットの必須チェックを満たさないと公式ドキュメントで説明されています。

「手動実行では成功しているのにマージできない」という場合は、実行結果だけでなく、どのイベントから起動されたかを確認してください。([GitHub Docs][1])

必須チェックが必ず結果を返す設計に変更する

基本方針は、必須チェックを作成するワークフロー自体は毎回起動し、テストが必要かどうかをワークフロー内部で判断することです。

問題が起きる設定例

次の例では、srcまたはtestsに変更がなければ、ワークフロー全体が起動しません。

name: ci

on:
  pull_request:
    paths:
      - 'src/**'
      - 'tests/**'

jobs:
  required-test:
    name: required-test
    runs-on: ubuntu-latest
    steps:
      - run: npm test

ルールセットでrequired-testを必須にしている場合、ドキュメントだけの変更では結果が報告されず、マージが止まる可能性があります。

ワークフローを常に起動する設計例

次の例では、プルリクエストごとにワークフローを起動し、ジョブの中で対象ファイルの変更を確認します。

name: ci

on:
  pull_request:

jobs:
  required-test:
    name: required-test
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Determine whether tests are required
        id: changes
        shell: bash
        env:
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
        run: |
          set +e
          git diff --quiet "$BASE_SHA" "$HEAD_SHA" -- src/ tests/
          diff_status=$?
          set -e

          case "$diff_status" in
            0)
              echo "run_tests=false" >> "$GITHUB_OUTPUT"
              ;;
            1)
              echo "run_tests=true" >> "$GITHUB_OUTPUT"
              ;;
            *)
              exit "$diff_status"
              ;;
          esac

      - name: Run tests
        if: steps.changes.outputs.run_tests == 'true'
        run: npm test

      - name: Record that tests were not required
        if: steps.changes.outputs.run_tests != 'true'
        run: echo "No relevant changes. Tests were not required."

これは設計例です。src/tests/npm testは、実際のディレクトリとテストコマンドに置き換えてください。

この構成では、対象ファイルに変更がない場合でもrequired-testジョブそのものは起動します。変更がなければテストステップだけを省略し、ジョブは成功結果を返します。

重要なのは、テストを無理に毎回実行することではありません。必須チェックのジョブを毎回作成し、テスト不要の場合にも明示的な完了結果を返すことです。

複数ジョブがある場合は最終判定用ジョブを用意する

ビルド、テスト、静的解析などを複数ジョブに分けている場合は、ルールセットで各ジョブを個別に必須化するより、最終判定用の固定ジョブを用意する方法もあります。

たとえば、次のような構成です。

changes
├── unit-test
├── lint
└── required-gate

required-gateを必須チェックにし、前段ジョブの結果を確認して最終結果を返します。

ただし、依存先のジョブが失敗すると、通常は後続ジョブもスキップされる可能性があります。必須チェックにする最終ジョブでは、必要に応じてalways()を使い、依存ジョブが失敗した場合でも最終判定処理自体は実行されるようにします。

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm test

  required-gate:
    name: required-gate
    if: ${{ always() }}
    needs:
      - test
    runs-on: ubuntu-latest
    steps:
      - name: Evaluate results
        shell: bash
        run: |
          case "${{ needs.test.result }}" in
            success)
              exit 0
              ;;
            *)
              exit 1
              ;;
          esac

テスト不要時のskippedを成功として扱う設計にする場合は、どの条件によるスキップを許可するかを明確にしたうえで判定処理を追加します。

単純にすべてのskippedを成功として扱うと、設定ミスによって重要なテストが実行されなかった場合まで通過させるおそれがあります。

GitHub公式ドキュメントでも、失敗したジョブに依存する必須ジョブでは、needsalways()を組み合わせる方法が案内されています。([GitHub Docs][1])

ルールセットとワークフローの条件を一致させる

必須チェックが待機状態になったとき、最も簡単に見える対処はルールセットから必須チェックを削除することです。

しかし、テスト対象外の変更があるたびに必須設定を外す運用では、設定の戻し忘れや、本来必要なテストの見落としにつながります。

次の考え方で設定をそろえます。

要件推奨する設計
すべてのプルリクエストで確認結果が必要ワークフローを毎回起動する
特定ファイルの変更時だけ重いテストが必要ワークフロー内部で変更ファイルを判定する
テスト不要時にもマージ条件を満たしたい固定名の必須ジョブから成功結果を返す
複数のテスト結果をまとめたい最終判定用のゲートジョブを必須にする
外部CIとGitHub Actionsを併用しているチェック名と提供元を分ける
複数ブランチにルールセットを適用する各ブランチでワークフローが起動するか確認する

必須チェックの役割は「必ず重いテストを実行すること」ではなく、「マージしてよい状態かを必ず報告すること」です。

テスト不要という判断も、ワークフローが起動しなかったことで暗黙的に表現するのではなく、ジョブの成功結果として明示的に報告する設計が適しています。

マージキューを使う場合はmerge_groupを追加する

マージキューを利用している場合、pull_requestイベントだけでは不十分です。

マージキューでは、キュー内の変更を組み合わせた一時的なマージグループに対してチェックが実行されます。GitHub Actionsのチェックを必須にしている場合は、ワークフローのトリガーにmerge_groupを追加します。

on:
  pull_request:
  merge_group:

merge_groupがないと、プルリクエスト単体ではチェックが成功していても、マージキューへ追加した後に必須チェックが報告されず、マージに失敗する可能性があります。

なお、pull_requestイベント用に作成した変更ファイル判定処理が、merge_groupイベントでもそのまま動くとは限りません。イベントごとに利用できるペイロードが異なるため、SHAの取得方法や差分判定処理も確認してください。([GitHub Docs][1])

よくある症状と確認ポイント

症状主な原因確認・修正内容
ドキュメント変更だけでチェックが待機するpathspaths-ignoreでワークフロー全体が省略されたワークフローを常時起動し、内部でテスト要否を判定する
過去のテストは成功しているのにマージできない最新コミットSHAに結果がない最新コミットに対して対象イベントを発生させる
Actionsの手動実行は成功しているworkflow_dispatchの結果が必須チェックとして評価されていないpull_requestなど対応イベントから起動する
マージキューへ入れると失敗するmerge_groupイベントがないワークフローのトリガーへmerge_groupを追加する
同名のチェックが成功しているのに認識されないGitHub Appの提供元が異なるルールセットで指定された提供元を確認する
ジョブ名変更後から待機するルールセットに旧チェック名が残っている実際に報告されるチェック名へ更新する
後続の必須ジョブが表示されない依存元の失敗で後続ジョブもスキップされた最終判定ジョブにalways()を検討する
mainでは動くがdevelopでは待機するワークフローとルールセットの対象ブランチが不一致branchesとルール適用対象を照合する

修正後に確認する手順

設定を変更した後は、次の順番で確認します。

  1. ルールセットまたはブランチ保護で、必須チェックの名前と提供元を確認する
  2. プルリクエストの最新コミットSHAを確認する
  3. .github/workflows内のpathsbranches、コミットスキップ条件を確認する
  4. 必須チェックを作成するワークフローが対象PRで必ず起動するように修正する
  5. テスト不要時は、ジョブまたはステップの条件分岐で成功結果を返す
  6. マージキューを使用している場合はmerge_groupを追加する
  7. 修正コミットをプッシュし、最新コミットに対してチェックが作成されたことを確認する
  8. プルリクエストのチェック欄で、必須チェックが成功または許容されたスキップとして完了したことを確認する

必須チェックが待機中のまま変わらない場合は、テスト内容より先に「そのチェックが作成されているか」を確認してください。

ワークフロー全体を省略する条件と、ルールセットで常に要求する必須チェックは、そのままでは両立しない場合があります。ワークフローを確実に起動し、不要な処理だけを内部で省略する構成に変更することで、テストコストを抑えながらマージ要件も安定して満たせます。
[1]: https://docs.github.com/en/pull-requests/how-tos/merge-and-close-pull-requests/troubleshooting-required-status-checks “Troubleshooting required status checks – GitHub Docs”
[2]: https://docs.github.com/en/pull-requests/reference/status-checks “Status checks – GitHub Docs”
[3]: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax “Workflow syntax for GitHub Actions – GitHub Docs”
[4]: https://docs.github.com/en/actions/how-tos/manage-workflow-runs/skip-workflow-runs “Skipping workflow runs – GitHub Docs”

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次