Claude Media
ClaudeでGitHub Actionsのフレークを見分ける — 失敗ログの渡し方

ClaudeでGitHub Actionsのフレークを見分ける — 失敗ログの渡し方

gh run viewで同一コミットの試行ごとの結果とログを集め、Claudeに渡してフレークと本物の失敗を切り分ける手順を、スクリプトと判定表で示します。

GitHub Actionsが赤になったとき、最初に知りたいのは「コードのせいか、たまたまか」です。この切り分けはClaudeに任せられますが、ログを丸ごと貼るだけでは精度が出ません。材料として必要なのは、同じコミットで試行ごとに結果が割れたかどうかと、失敗した箇所のログです。

gh run viewとgh run listでその材料を集め、claude -pに渡して「フレーク候補 / 本物候補 / 判断不能」に分類させる流れを、コピーして使える形で説明します。最後に、失敗をきっかけにワークフロー内で同じ分類を自動実行する構成も扱います。

フレークと本物は何で分けるのか

フレークとは、コードを変えずに再実行すると結果が変わる失敗のことです。したがって判定の軸は「同じコミットで、成功と失敗の両方が出ているか」になります。ログの文面は補助の証拠です。

次の表は、証拠ごとに見える傾向をまとめたものです。表の読み方は目安であり、ひとつで断定するものではありません。

証拠フレーク寄り本物寄り
同一コミットの試行結果フレーク寄り失敗のあとに成功が出る本物寄りすべての試行で失敗
失敗箇所フレーク寄り試行ごとに違うテスト・ステップ本物寄り毎回同じテスト・同じ行
エラーの種類フレーク寄りタイムアウト、接続リセット、レート制限本物寄りアサーション失敗、コンパイルエラー
過去の履歴フレーク寄り別のコミットでも同じジョブが散発的に赤本物寄りある時点から連続して赤

GitHub Actionsでは、ひとつの実行(run)を再実行するたびに試行番号(attempt)が増えます。gh run viewは--attemptでその番号を指定でき、JSON出力のフィールドにもattemptとheadSha、conclusionが含まれています。表の1行目と2行目は、この2つで機械的に取れます。

一方、3行目と4行目はログの読解が要ります。ここが「Claudeに渡す」意味のある部分です。

手順1: 失敗した実行の試行結果を集める

まず、失敗した実行のIDからコミットSHAを取り、同じSHAのほかの実行も並べます。gh run listには--commitでSHAを絞る指定があります。

RUN=123456789   # 赤くなった実行のID
SHA=$(gh run view "$RUN" --json headSha --jq .headSha)
 
# 同じコミットで走った実行を一覧する
gh run list --commit "$SHA" \
  --json databaseId,workflowName,attempt,conclusion \
  --jq '.[] | [.databaseId, .workflowName, .attempt, .conclusion] | @tsv'

gh run listは直近の実行を既定で20件取得します。--limitで件数を増やせるので、プッシュが多いリポジトリでは広げてください。

次に、同じ実行の試行ごとにジョブの結果を取ります。gh run viewの--json jobsでジョブ一覧が取れ、--attemptで試行を選べます。

LAST=$(gh run view "$RUN" --json attempt --jq .attempt)
for n in $(seq 1 "$LAST"); do
  echo "## attempt $n"
  gh run view "$RUN" --attempt "$n" --json jobs \
    --jq '.jobs[] | "\(.name)\t\(.conclusion)"'
done

この出力で「attempt 1はtest (node 20)だけ失敗、attempt 2は全部成功」のような形が見えれば、フレーク候補としてかなり絞れます。全試行が同じジョブで失敗しているなら、本物候補です。

手順2: 失敗ステップのログだけを取る

ログ全体は数万行になることがあります。gh run view --log-failedは、失敗したステップのログだけを出力します。

gh run view "$RUN" --attempt 1 --log-failed > attempt1.log
gh run view "$RUN" --attempt "$LAST" --log-failed > attempt-last.log
wc -l attempt1.log attempt-last.log

ここで覚えておきたい仕様が2つあります。GitHub CLIのマニュアルによると、ログをzip形式で取る主な方法でジョブとログを対応づけられないとき、ghはジョブごとにAPIで取得し直します。この代替経路は遅く、ログが取れていないジョブが25件を超えると、操作はエラーで終わります。また、ステップに紐づかない行は、ステップ名がUNKNOWN STEPと表示されます。出力にこの文字列が混じっていても、取得に失敗したわけではありません。

最後の試行が成功しているときは、失敗した試行の番号を--attempt 1のように指定してログを取ります。

失敗したジョブが多い実行では、--jobでジョブIDを指定すると、そのジョブのログだけを取れます。--log-failedと組み合わせれば、調べたいジョブの失敗ステップだけが手に入ります。ジョブIDは、手順4で触れるdatabaseIdと同じ値です。

スクリプトの中で成否を分岐したいときは、gh run viewに--exit-statusを付けます。実行が失敗していると非ゼロの終了コードで終わるので、ifで判定を書けます。&&でつなげば、実行が失敗していないとき(実行中か成功)だけ続きの処理を動かせます。

手順3: 証拠をまとめてClaudeに渡す

集めた材料をひとつのファイルにまとめます。長いログは、失敗ステップの末尾に原因が出ることが多いので、各ログの後ろ側だけを入れると扱いやすくなります。

{
  echo "# 実行 $RUN / コミット $SHA"
  echo "## 同一コミットの実行一覧"
  gh run list --commit "$SHA" \
    --json databaseId,workflowName,attempt,conclusion \
    --jq '.[] | [.databaseId, .workflowName, .attempt, .conclusion] | @tsv'
  echo "## 試行ごとのジョブ結果"
  for n in $(seq 1 "$LAST"); do
    echo "### attempt $n"
    gh run view "$RUN" --attempt "$n" --json jobs \
      --jq '.jobs[] | "\(.name)\t\(.conclusion)"'
  done
  echo "## attempt 1 の失敗ログ(末尾300行)"
  tail -n 300 attempt1.log
} > evidence.md

あとはclaude -pに渡すだけです。ヘッドレスモードでは、標準入力にファイルを流して指示を添える使い方ができます。スクリプトやCI向けには--bareが推奨されており、フックやプラグインの自動読み込みを省いて起動が速くなります。

cat evidence.md | claude --bare -p \
  'この資料はGitHub Actionsの失敗の証拠です。
失敗を flaky / real / unknown のどれかに分類してください。
根拠は資料中の行をそのまま引用し、資料にないことは推測しないでください。
同一コミットで成功した試行があるかを最初に確認してください。
判断できないときは unknown とし、追加で必要な情報を書いてください。'

指示の中で重要なのは、最後の2文です。「引用させる」と「unknownを許す」の2つがないと、Claudeは何かしらの結論を無理に出しがちです。資料に書いてある文字列だけを根拠にさせると、出力を人間が検算しやすくなります。

結果を機械で読みたいとき

分類結果をスクリプトで使うなら、--output-format jsonと--json-schemaを組み合わせます。応答のうち構造化された部分はstructured_outputフィールドに入ります。

cat evidence.md | claude --bare -p '失敗を分類してください。根拠は資料の引用で示すこと。' \
  --output-format json \
  --json-schema '{"type":"object","properties":{"verdict":{"type":"string","enum":["flaky","real","unknown"]},"evidence":{"type":"array","items":{"type":"string"}},"next_step":{"type":"string"}},"required":["verdict","evidence","next_step"]}' \
  | jq '.structured_output'

応答にstructured_outputが付かないことがあります。たとえばClaudeが依頼を断った場合です。後続の処理に渡す前に、フィールドの有無を確認してください。

手順4: 再実行で確かめる

分類は仮説です。最終的には再実行の結果で裏づけます。gh run rerunは、失敗したジョブだけを再実行できます。

# 失敗したジョブとその依存ジョブだけを再実行
gh run rerun "$RUN" --failed
 
# 原因調査用にデバッグログを有効にして再実行
gh run rerun "$RUN" --failed --debug

--failedは失敗ジョブと、それに依存するジョブだけを再実行します。成功済みのジョブを回し直さないので、実行時間も分数も節約できます。個別のジョブを指すときは--jobを使いますが、ここに渡す値は、ブラウザのURLに出てくる番号ではありません。マニュアルが示すとおり、gh run view <run-id> --json jobs --jq '.jobs[] | {name, databaseId}'で出したdatabaseIdを使います。URLの番号を渡すとAPIが404を返します。

再実行して通ったら、フレーク判定にもう一段の確度がつきます。ただし1回の成功は「かなり怪しい」止まりです。同じジョブが別のコミットでも散発的に赤くなっているかをgh run list --workflowと--status failureで数えると、傾向が見えてきます。

失敗のたびに自動で分類させる

手元で集める手順を毎回行うのは面倒です。ClaudeのGitHub Actionsは、ワークフローにpromptを渡すと自動化モードで動き、メンションを待たずに実行できます。GitHubのイベントであれば何でも起点にでき、スケジュール実行も含まれます。

たとえばCIワークフローの完了をきっかけに、失敗時だけ分類を走らせる構成は次のような形になります。以下は設定項目の組み合わせ例であり、ご自身のリポジトリで動作を確認してから使ってください。ワークフローYAMLの書き方そのものをClaude Codeに任せる手順はGitHub Actionsのワークフロー作成の記事にあります。

name: CI Failure Triage
on:
  workflow_run:
    workflows: ["CI"]
    types: [completed]
jobs:
  triage:
    if: github.event.workflow_run.conclusion == 'failure'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      actions: read
      id-token: write
    env:
      GH_TOKEN: ${{ github.token }}
    steps:
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          prompt: |
            実行 ${{ github.event.workflow_run.id }} が失敗しました。
            gh run view と gh run list で同一コミットの試行結果と
            失敗ログを調べ、flaky / real / unknown に分類してください。
            根拠はログの引用で示し、推測は書かないでください。
          claude_args: |
            --max-turns 8
            --allowedTools "Bash(gh run view *),Bash(gh run list *)"

各設定の意味は次のとおりです。

  • actions: read: ClaudeがCIの結果を読むための権限です
  • id-token: write: Claude Code GitHub Actionが既定で使うGitHub App認証に必要な権限です
  • GH_TOKEN: ghコマンドが認証に使う環境変数です。設定しておくと認証の入力を求められません
  • --allowedTools: 通常の文章のpromptでは、ClaudeにシェルやGitHub APIへのアクセスは与えられていません。使わせるツールをclaude_argsで明示します。書き方はBash(コマンド *)で、ここでは読み取りだけに絞っています。渡し方の詳細はパラメータの記事にあります
  • --max-turns: 暴走したときの上限で、コスト管理のために設定します

rerunを許可しないのは意図的です。分類の結果をもとに自動で再実行までさせると、本物の失敗を何度も回して分数を消費する恐れがあります。再実行は人間が判断するほうが安全です。

結果の出る場所

自動化モードでは、結果は既定でワークフローの実行ログに出ます。プルリクエストやIssueに書き込ませたいときは、プロンプトでそう指示し、投稿できるツールも許可する必要があります。実行ログに出すだけなら追加の権限はいりません。まずはここから始めると、書き込み権限の設計を後回しにできます。

つまずきやすい点

落とし穴

再実行で通った失敗が、すべてフレークとは限らない

競合状態やタイミング依存のバグは、再実行で通ることがあります。それでもコードの不具合です。同じテストが何度も赤くなる、特定の並列数や実行順で出る、といった傾向があるなら、フレークとして放置せず原因を調べる対象にしてください。Claudeに分類させるときも、「flakyならなぜ起きうるかの仮説を書く」ことまで求めると、見逃しが減ります。

ほかに、次の点で結果が崩れます。

  • ボットが起点のとき: ClaudeのGitHub Actionsは、ボットが起こした実行をallowed_botsに登録しない限り拒否します。Dependabotのプルリクエストで赤くなった実行を分類させたいときは、この設定が要ります。スケジュール実行も、GitHubが実行ユーザーとして扱う相手がボットだと同じ拒否に当たります
  • ログが長すぎるとき: 末尾だけを渡す運用でも、原因が先頭側にあるテストはあります。unknownが続くときは、渡す範囲を広げてください
  • ワークフローが組織の共有設定のとき: 認証情報の置き場所は組織単位で変わります。導入手順は組織向けの設定記事にあります

まとめ

分類の精度を決めるのはモデルより、証拠ファイルの作り方です。試行ごとの結果の表が入っていない資料ではunknownが増えるので、まずスクリプトの出力を見直してください。flakyが続いたテストの名前を集め、リトライ設定を入れるか、テスト自体を直すかを決めるところからが本番です。

参照した一次ソース

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

  1. code.claude.com/docs/en/github-actions
  2. code.claude.com/docs/en/headless
  3. cli.github.com/manual/gh_run_view
  4. cli.github.com/manual/gh_run_rerun
この記事を共有:XはてブLinkedIn