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が続いたテストの名前を集め、リトライ設定を入れるか、テスト自体を直すかを決めるところからが本番です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。