Claude Media
Claude Codeで調べるGitLab CIの失敗 — glabでログを取り原因を絞る

Claude Codeで調べるGitLab CIの失敗 — glabでログを取り原因を絞る

GitLab CIが落ちたとき、glab ci traceでログを端末に取り、rules・needs・cacheのどれが原因かを切り分ける手順です。ログをClaude Codeに読ませる設定例も載せます。

GitLab CIのパイプラインが赤くなったら、ブラウザを開かずにglab ciでジョブのログを端末へ取り、Claude Codeに読ませて原因を絞れます。.gitlab-ci.ymlの失敗は、突き詰めるとrulesの条件漏れ、needsの依存、cacheのキーの不一致のどれかに落ちることが多いです。この記事では、その3つを症状から切り分ける順番と、ログをClaude Codeに渡す設定をまとめます。

@claudeメンションでMRを自動化する側の話は、Claude CodeとGitLab CI/CDの連携にあります。ここで扱うのは逆向きで、手元のClaude Codeが、すでに落ちたパイプラインを調べる場面です。

glab ciで失敗ジョブのログを端末に取る

glabはGitLabのコマンドラインツールで、glab ci以下にパイプラインとジョブを扱うコマンドが並びます。失敗の調査に使うのは次の5つです。インストールと認証の準備はClaude CodeとGitLabの連携方法で触れているので、glab auth loginが済んでいる前提で進めます。

やりたいことコマンド
失敗したパイプラインを探すコマンドglab ci list --status=failed
パイプライン内の失敗ジョブを見るコマンドglab ci get --status=failed --with-job-details
ジョブのログを流すコマンドglab ci trace <job-id または job名>
設定ファイルを検証するコマンドglab ci lint --dry-run --ref <ブランチ>
直したジョブだけ再実行するコマンドglab ci retry <job-id または job名>

glab ci traceは、ジョブのログを端末へ流すコマンドです。引数なしで実行すると、ジョブを対話的に選べます。ジョブIDかジョブ名を渡せば選択を飛ばせます。

# 現在のブランチのパイプラインから、失敗ジョブだけを一覧にする
glab ci get --status=failed --with-job-details
 
# ジョブ名を指定してログを見る(例: test というジョブ)
glab ci trace test
 
# パイプラインIDを指定して探す
glab ci trace test --pipeline-id 12345

ジョブの検索先は、既定で現在のブランチです。--branchでブランチを、--pipeline-idでパイプラインを指定できます。

落とし穴

MRのパイプラインがブランチ指定で見つからない

フォークからのMRや、detachedパイプラインでは、MRのhead pipelineがソースブランチの最新パイプラインと食い違うことがあります。glab ci getには--merge-requestがあり、MRのIIDを渡すとそのhead pipelineを対象にできます。glab ci get --merge-request=42 --status=failed --with-job-detailsのように使います。

3つの原因を症状から振り分ける

ログを開く前に、症状でおおよその当たりを付けられます。失敗の見え方が3つで違うためです。

症状疑う先最初に打つコマンド
期待したジョブがパイプラインに出てこない疑う先rules最初に打つコマンドglab ci lint --dry-run --ref <ブランチ>
パイプライン自体が作られない、または順序がおかしい疑う先needs最初に打つコマンドglab ci list --yaml-errors
ジョブは動くが依存の再取得で遅い、ファイルが無い疑う先cache最初に打つコマンドglab ci trace <ジョブ名>

rulesの問題は「失敗」ではなく「不在」として現れます。ログを探しても何も無いので、取るべきは設定のシミュレーション結果です。needsの問題は、パイプラインの作成時点で弾かれることがあります。cacheは、ジョブが動いたうえで効いていない形になるので、ログを読む価値があるのはこの層です。

rules: ジョブがパイプラインに入っていない

rulesは先頭から順に評価され、最初に一致したルールで、ジョブが含まれるか除外されるかが決まります。どれにも一致しなければ、そのジョブはパイプラインに追加されません。ログが無いのはこのためです。

次の例は、MRパイプラインでは手動ジョブとして、スケジュール実行では通常ジョブとして動く設定です。

job:
  script: echo "Hello, Rules!"
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: manual
      allow_failure: true
    - if: $CI_PIPELINE_SOURCE == "schedule"

ブランチへのpushで走るパイプラインには、どちらのルールも一致しません。このジョブはそこに現れません。調べる手順は次のとおりです。

手順

rulesの条件漏れを探す手順

  1. 1

    ブランチを指定してシミュレーションする

    glab ci lint --dry-run --ref <ブランチ> --include-jobsを実行します。--dry-runはパイプライン作成のシミュレーションで、--refでブランチかタグの文脈を決めます。--include-jobsを付けると、そのパイプラインに存在するジョブの一覧が出ます。

  2. 2

    期待したジョブが一覧にあるか見る

    無ければ、そのジョブのrulesがどの条件にも一致していません。パイプラインの種類($CI_PIPELINE_SOURCE)がpush、MR、スケジュールのどれかを、実行されたパイプラインに合わせて確かめます。

  3. 3

    when: neverの並びを確認する

    when: neverを先頭に置いたルールが一致すると、後ろのルールは評価されません。除外条件が広すぎないかを、上から順に読みます。

  4. 4

    スクリプトで作った変数をルールに使っていないか見る

    ジョブのスクリプト内で作ったdotenv変数は、rulesで使えません。ルールはどのジョブよりも先に評価されるためです。

glab ci listの--sourceオプションを使うと、merge_request_event、push、triggerといった起動元でパイプラインを絞れます。「MRでは走るのにpushでは走らない」という食い違いを、実際の実行履歴から見つけたいときに便利です。

needs: 存在しないジョブへの依存

needsはステージの順序を飛ばして、ジョブを先に動かすキーワードです。落とし穴は、needsで指したジョブがrulesで外れている場合に現れます。つまりrulesとneedsの合わせ技です。

needsの対象がonly、except、rulesで除外されうるジョブだと、パイプラインの作成に失敗することがあります。必須のままで、そのジョブがパイプラインに無いと、次のようなエラーで起動しません。

'job1' job needs 'job2' job, but it was not added to the pipeline

対処は、依存先が無くても動いてよいならoptional: trueを付けることです。

test-job:
  needs:
    - job: build-job
      optional: true
  script: echo "test"

依存先が存在するときは、完了を待ってから始まります。存在しなければ、他のneedsが満たされた時点で始まります。needsの中身がすべて任意で、どれもパイプラインに無い場合は、needs: []と同じく即座に始まります。

もうひとつ、needsを付けたジョブは、前のステージの成果物を自動では受け取りません。needsに列挙したジョブの成果物だけをダウンロードします。needsを足したあとで「ファイルが見つからない」と落ちるなら、依存先の列挙漏れを疑います。

needsに書ける要素は最大50個です。dependenciesと同じジョブで併用しない指針もあります。

パイプラインが作られない場合、ジョブのログは存在しません。glab ci list --yaml-errorsは、設定が不正なパイプラインだけを返します。glab ci lintで.gitlab-ci.ymlを手元から検証して、作成時のエラーを再現してください。

cache: ジョブは動くが効かない

cacheの不一致は、ジョブがエラーで止まるより、毎回依存を取り直して遅くなる形で現れます。ジョブが動いている以上、glab ci traceでログを読み、依存の取得に時間を食っていないかを見るのがここでの出番です。

キャッシュは最適化であり、常に効くとは限りません。実際に使えるかどうかは、ランナーのexecutorの種類と、ジョブ間で同じランナーが使われるかに左右されます。公式のトラブルシューティングには、不一致の原因がいくつか挙がっています。

  • 分散キャッシュ無しで、複数のスタンドアロンランナーが1つのプロジェクトにつながっている
  • 分散キャッシュ無しで、オートスケールのランナーを使っている
  • ランナーのマシンかS3バケットの空き容量が足りない
  • 異なるパスをキャッシュするジョブが、同じキーを使っている

最後のものは、設定ファイルの側で直せます。同じキーで別のパスを保存すると、アーカイブが上書きされます。

前と後

キーを共有していて、キャッシュが上書きされる

BEFORE

同じキーで別のパスを保存

build-job:
  cache:
    key: shared
    paths: [vendor/]
 
test-job:
  cache:
    key: shared
    paths: [node_modules/]
AFTER

ジョブごとにキーを分ける

build-job:
  cache:
    key: build-$CI_COMMIT_REF_SLUG
    paths: [vendor/]
 
test-job:
  cache:
    key: test-$CI_COMMIT_REF_SLUG
    paths: [node_modules/]

依存ファイルの内容でキーを決めるなら、cache:key:filesを使います。列挙したファイルの内容からSHAでキーが作られ、内容が変わらなければブランチやパイプラインをまたいで同じキーになります。Gemfile.lockやpackage-lock.jsonを指定する使い方が典型です。注意点が2つあります。

  • ファイルの個数には上限があり(GitLabのバージョンと機能フラグで変わります)、CI/CD変数は使えません
  • 指定したファイルがどれも存在しないとき、キーはdefaultになります

キーが一致しないときの保険には、fallback_keysが使えます。1つのキャッシュエントリに最大5個の代替キーを指定でき、主キーが見つからなければ順に試されます。どれも見つからなければ、キャッシュ無しで依存を取得し、ジョブの終了時に新しいキャッシュを作ります。

なお、同じパスをcacheとartifactsの両方で保存すると、キャッシュが上書きされることがあります。キャッシュはartifactsより先に復元されるためです。

ログをClaude Codeに読ませて原因を絞る

ログの読み取りは、Claude Codeの非対話モードに渡すのが手早い方法です。標準入力でログを渡し、-pで指示文を与えます。Claude Codeの公式の例にも、ビルドエラーのファイルをパイプで渡して根本原因を説明させる形があります。

# ログの末尾だけを切り出してファイルに保存する
glab ci trace test | tail -n 300 > /tmp/ci-test.log
 
# 設定ファイルと一緒に原因候補を挙げさせる
cat /tmp/ci-test.log | claude -p \
  "このCIログの失敗の根本原因を3行で説明し、.gitlab-ci.ymlの該当箇所を示して"

ログ全体ではなくtailで末尾だけを渡すのは、巨大なログでトークンを使い切らないためです。失敗の直前の出力は、ログの末尾に集まります。

調査を対話セッションで続けるなら、読み取り系のglabコマンドだけを許可しておくと、確認の回数が減ります。permissions.allowの書式は、サブコマンドの後ろに*を置くプレフィックス一致です。次の例は.claude/settings.jsonに入れる形です。

{
  "permissions": {
    "allow": [
      "Bash(glab ci list *)",
      "Bash(glab ci get *)",
      "Bash(glab ci trace *)",
      "Bash(glab ci lint *)"
    ]
  }
}

glab ci retryとglab ci runは、パイプラインを実際に動かします。これらをallowに入れなければ、通常の権限モードでは実行前に確認が入ります。意図しない再実行を避けたいリポジトリでは、この状態を保つほうが安全です。

リポジトリのCLAUDE.mdには、調べる順番を書いておくと、毎回同じ手順で動きます。

## CIが落ちたとき
1. `glab ci get --status=failed --with-job-details` で失敗ジョブを特定する
2. ジョブが出てこないなら `glab ci lint --dry-run --ref <ブランチ> --include-jobs`
3. `glab ci trace <ジョブ名>` の末尾300行から原因を絞る
4. 直したら `glab ci lint` で検証してから、再実行はユーザーに確認する

この手順の肝は、ログを読む前に症状で3つに振り分けることです。Claude Codeにログだけを渡すと、rulesの不在のような「ログが無い失敗」を取りこぼします。

CI上のClaudeジョブ自体が落ちる場合

ここまでは、アプリのテストやビルドのジョブが落ちる話でした。GitLab CI/CDにClaude Codeを組み込んでいるなら、Claudeを動かすジョブ自体が落ちるケースもあります。公式のトラブルシューティングは、次の3つを挙げています。

  • @claudeに反応しない: パイプラインが起動しているか、ANTHROPIC_API_KEYなどの変数が入っているか、コメントが/claudeでなく@claudeか
  • コメントやMRを書けない: CI_JOB_TOKENの権限、またはapiスコープのProject Access Token。--allowedToolsへのmcp__gitlabの追加
  • 認証エラー: Claude APIならキーの有効期限。BedrockやGoogle CloudならOIDC/WIFの設定とリージョン

これらはrules・needs・cacheとは別の層の問題です。ジョブのログを開いて認証系の文言が出ているなら、こちらを先に当たります。Bedrockの認証はGitLab CI/CDでBedrock OIDCを組む手順に詳しくあります。

まとめ

GitLab CIの失敗調査は、ログを開く前に症状で3つへ振り分けるほうが速く終わります。ジョブが出てこないならrulesで、glab ci lint --dry-runで確かめます。パイプラインが作られないならneedsで、optional: trueが効く場面かを見ます。動いているのに遅い、ファイルが無いならcacheで、キーとランナーの構成を疑います。

Claude Codeに任せる範囲は、読み取り系のコマンドまでにとどめるのが扱いやすい構成です。再実行は人が決めると、赤いパイプラインを闇雲に回し続ける事態を避けられます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. docs.gitlab.com/cli/ci/trace
  2. docs.gitlab.com/cli/ci/get
  3. docs.gitlab.com/cli/ci/lint
  4. docs.gitlab.com/ci/jobs/job_rules
  5. docs.gitlab.com/ci/yaml
  6. docs.gitlab.com/ci/caching
  7. code.claude.com/docs/en/gitlab-ci-cd
  8. code.claude.com/docs/en/headless
この記事を共有:XはてブLinkedIn