AIが書いたコードがローカルでは通るのにGitHub Actionsで落ちるときの調べ方
AIを使いこなすClaudeで執筆し、GPTで自動校閲・修正しています。

結論から言うと、原因の多くは「コードの誤り」ではなく「実行条件の違い」です。まず失敗した最初のステップのログを読み、次にローカルとCIで違う条件(OS、Nodeなどのバージョン、依存関係のインストール方法、環境変数、ファイルの有無)を表で洗い出します。この順番で進めると、AIに「直して」と丸投げするより、短い手数で原因を絞れます。 この記事の前提は、GitHub Actions上でテストが落ち、手元では通るケースです。ログの読み方と差分の洗い出しに絞ります。表と手順は一般的な切り分けの考え方をまとめた編集上の整理で、特定のリポジトリで動作確認したものではありません。
この記事の目次
1. まず「CIは毎回まっさらな環境」だと押さえる
GitHub-hostedランナーは、多くの場合ジョブごとに新しい仮想マシンです。公式ドキュメントでは、GitHubホストランナーは新しいVMとして用意されると説明されています。また ubuntu-latest は「GitHubが提供する最新の安定イメージ」を指し、OSベンダーの最新版とは限りません。
つまり、手元にだけ存在する次のものはCIにありません。
- 以前インストールしたままのパッケージや node_modules
- .gitignore で除外した設定ファイル(.env など)
- ローカルDBやキャッシュに残っていたデータ
AIが書いたコードは「動いた環境」を前提にしがちで、こうした暗黙の依存を含みやすい点に注意します。
2. ログを読む順序のチェックリスト
ログは Actionsタブから対象の実行→ジョブを開くと、失敗したステップが自動で展開されます。次の順に確認してください。
- 最初に赤くなったステップを特定する。 テストではなく、チェックアウト、セットアップ、インストール、ビルドの段階で落ちていないか見ます。後続の失敗は連鎖であることが多いです。
- そのステップの最初のエラー行を探す。 末尾の「Process completed with exit code 1」は結果にすぎません。最初に出たエラーが手掛かりです。
- 実行環境の表示を確認する。 OS、ランタイムのバージョン、実行したコマンドを、手元と見比べます。
- インストールの警告を見る。 依存関係の不整合や取得失敗がないか確認します。
- 失敗したテスト名と期待値・実際値を控える。 毎回同じ箇所か、実行のたびに変わるかも記録します。
- ここまでで分からなければ詳細ログを有効にする。 再実行時のデバッグログ有効化、または ACTIONS_STEP_DEBUG を true にする方法があります。詳しくは公式のデバッグログの説明を参照してください。
ログ内の検索は、展開されているステップだけが対象です。見つからないときは、関係しそうなステップを開いてから検索します。コマンドラインでは gh run view 実行ID --log-failed で失敗したステップのログだけを表示できます。
3. ローカルとCIの条件の差分表
ログから見当が付かないときは、下の表を埋めて比較します。右端は、見つかった場合の主な確認先です。
- OS、ローカルで起きがちなこと:macOS/Windowsで開発、CIでの違い:多くはLinux。ファイル名の大文字小文字を区別する、確認先:import文のパスと実際のファイル名
- ランタイム、ローカルで起きがちなこと:古いNodeなどが入っている、CIでの違い:ワークフローで指定したバージョン、確認先:ワークフローのセットアップ設定、手元の node -v
- 依存関係、ローカルで起きがちなこと:既存の node_modules を使用、CIでの違い:毎回新規インストール、確認先:ロックファイルがコミットされているか
- インストール方法、ローカルで起きがちなこと:npm install、CIでの違い:npm ci を使うことが多い、確認先:下記参照
- 環境変数、ローカルで起きがちなこと:.env やシェル設定がある、CIでの違い:CI=true などが設定され、秘密情報は未設定のことがある、確認先:ワークフローの env、Secrets
- ファイル、ローカルで起きがちなこと:未コミットのファイルがある、CIでの違い:コミット済みのものだけ、確認先:git status、.gitignore
- 時刻・順序、ローカルで起きがちなこと:タイムゾーンや実行順が固定的、CIでの違い:異なる場合がある、確認先:日付を使うテスト、テスト間の依存
- 性能、ローカルで起きがちなこと:高性能な手元の機械、CIでの違い:リソースが異なり、遅いとタイムアウトしうる、確認先:タイムアウト設定
インストール方法の違いは特に見落としやすい
npm ciはロックファイルが必須で、package.jsonとロックファイルが食い違うとエラーになり、node_modulesを削除してからインストールします。手元の npm install はロックファイルを更新しうるため、AIにパッケージを追加させたのにロックファイルをコミットし忘れると、CIだけ落ちることがあります。
CI環境かどうかで挙動を分けるコードに注意
GitHub Actionsでは、CI と GITHUB_ACTIONS が常に true に設定されます。テストランナーや生成コードが CI の有無で挙動(監視モード、色、再試行など)を変えている場合があります。手元で CI=true を付けて同じコマンドを実行すると、再現することがあります。
4. 手元でCIに近づけて再現する
差分の見当が付いたら、手元をCIに近づけます。手順は次のとおりです。
- 変更を全てコミットし、git status が空であることを確認する。
- 別の場所にクローンし直す(git clone したてのフォルダ)。
- ワークフローと同じバージョンのランタイムに切り替える。
- ワークフローと同じ順のコマンドを、CI=true を付けて実行する(Node系なら npm ci → テスト)。
これで落ちれば、クリーンな環境でも再現することが分かります。依存関係やコミット漏れだけでなく、コードやテスト自体の問題も含めて調べます。落ちなければ、OSやリソース、Secretsなどのランナー固有の条件を疑います。
CIの環境をその場で確認したいときは、ワークフローの失敗前に診断用ステップを足す手もあります。次は説明用の例で、実行確認はしていません。
- name: 環境の診断(説明用の例)
run: |
echo "OS: $RUNNER_OS"
node -v
npm -v
echo "CI=$CI"
pwd
ls -la
git status --short出力にはパスなどが含まれるため、Secretsや認証情報は表示しないでください。原因が分かったら、このステップは削除します。
5. AIに渡す依頼文の例
ログをそのまま貼って「直して」と頼むと、AIは推測で本番コードを書き換えがちです。条件の差分を先に渡し、原因の仮説を挙げさせるようにします。以下は説明用の例です。
GitHub Actionsでだけテストが失敗します。ローカルでは成功します。
原因を断定せず、確認すべき順に仮説を3つまで挙げてください。
コードの修正案は、仮説ごとに確認方法を示してからにしてください。
【失敗したステップ】テスト実行(npm test)
【最初のエラー行】(ここに貼る。トークンや社内URLは伏せ字にする)
【ローカル】OS: macOS / Node: v○○ / 実行: npm install → npm test
【CI】OS: ubuntu-latest / Node: ワークフロー指定の値 / 実行: npm ci → npm test
【最近の変更】AIが追加した関数とテスト(差分を貼る)
【未コミットのファイル】(git statusの結果)人が確認する点は3つです。仮説がログの該当行に基づいているか、修正が「テストを通すためだけ」の変更(テストの削除、期待値の書き換え、タイムアウトの無制限化)になっていないか、秘密情報が含まれていないか、です。
6. 原因別の判断の目安
- インストール段階で失敗、まず疑うこと:ロックファイル未コミット、不整合、次の一手:ローカルで npm ci を試す
- Cannot find module、まず疑うこと:大文字小文字、コミット漏れ、次の一手:git ls-files で追跡対象を確認
- 環境変数が undefined、まず疑うこと:CIに設定がない、次の一手:必要な変数名を洗い出し、Secretsや env に設定
- 毎回同じテストだけ失敗、まず疑うこと:環境差か実際のバグ、次の一手:CI=true 付きでローカル再現
- 実行ごとに結果が変わる、まず疑うこと:順序依存、時間依存、非同期の待ち不足、次の一手:該当テストを単独で繰り返し実行
- タイムアウト、まず疑うこと:CIの処理速度、外部通信、次の一手:どの処理が遅いかをログの時刻で確認
どの行にも当てはまらないときは、デバッグログを有効にして再実行し、追加された出力から手順2に戻ります。

