| name | debug-gha |
| description | GitHub Actions のワークフロー失敗をデバッグする。フルログを ZIP でダウンロードしてローカルで調査する。Use when the user says 'CIが落ちた', 'GitHub Actionsをデバッグ', 'ワークフローが失敗', 'debug CI', 'debug GitHub Actions', 'CI failure', 'GHAのログを見て', 'actionsのエラーを調べて', or wants to investigate a GitHub Actions workflow failure. |
Debug GitHub Actions
GitHub Actions のワークフロー失敗の原因を特定し、修正するためのスキル。フルログを ZIP でダウンロードし、ローカルファイルとして読んで調査する。
このスキルのスコープ: run の特定 → フルログ取得 → エラー原因の特定 → コード修正の適用。
禁止事項
gh run view --log / gh run view --log-failed を使用しない
- 理由: 25 件超の job ログのフォールバック取得が必要な場合に失敗する。一部ステップが
UNKNOWN STEP になる
- ログを
grep / head / tail でフィルタしない
- 理由: エラーの前後コンテキストを見落とす。ログファイルは Read ツールで全文読む
- ファイルが大きい場合はサブエージェントに委譲して読ませる
ワークフロー
function debug_gha(input):
run = resolve_run(input)
if run is null:
report_to_user("対象の run が見つかりません")
return
log_dir = download_full_logs(run)
analysis = analyze_logs(run, log_dir)
report_findings_to_user(analysis)
if analysis.has_fixable_issues:
apply_fixes(analysis)
report_to_user("修正を適用しました: " + summary(analysis.fixes))
run の特定
run を特定する方法は 3 つある。優先順位順に試す。
function resolve_run(input):
// 方法 1: ユーザーが run ID を直接指定
if input.run_id:
return gh("run view {run_id} --json databaseId,name,conclusion,jobs")
// 方法 2: ユーザーが PR 番号を指定
if input.pr_number:
checks = gh("pr checks {pr_number} --json bucket,name,link")
failed = filter(checks, bucket == "fail" AND link != "")
if failed is empty:
report_to_user("PR #{pr_number} に失敗した Actions check はありません")
return null
// link は ".../actions/runs/{run_id}/job/{job_id}" 形式。run_id は URL 中間にある
// 非 Actions check(CodeRabbit 等)は link が空文字列のため上のフィルタで除外済み
run_id = extract_run_id_from_url(failed[0].link) // /runs/(\d+)/ をパターンマッチ
return gh("run view {run_id} --json databaseId,name,conclusion,jobs")
// 方法 3: 現在のブランチから自動検出
branch = git("branch --show-current")
runs = gh("run list --branch {branch} --status failure --limit 5 --json databaseId,name,conclusion,createdAt")
if runs is empty:
runs = gh("run list --status failure --limit 5 --json databaseId,name,conclusion,createdAt")
if runs is empty:
return null
return runs[0] // 最新の失敗 run
gh run list --branch "$(git branch --show-current)" --status failure --limit 5 --json databaseId,name,conclusion,createdAt
gh run view {run_id} --json jobs --jq '.jobs[] | {id: .databaseId, name, conclusion, steps: [.steps[] | {name, conclusion}]}'
フルログのダウンロード
gh api の repos/{owner}/{repo} プレースホルダーは gh CLI が自動解決する。
function download_full_logs(run):
log_dir = ".local-agents/tmp/gha-logs/" + str(run.databaseId)
mkdir_p(log_dir)
// ZIP でフルログをダウンロード
exec("gh api repos/{owner}/{repo}/actions/runs/" + str(run.databaseId) + "/logs > " + log_dir + "/logs.zip")
// 展開
exec("unzip -o " + log_dir + "/logs.zip -d " + log_dir + "/extracted/")
// ファイル一覧を確認して展開結果を検証
files = list_files(log_dir + "/extracted/")
if files is empty:
error("ログの展開に失敗しました")
return log_dir + "/extracted/"
LOG_DIR=".local-agents/tmp/gha-logs/${RUN_ID}"
mkdir -p "$LOG_DIR"
gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/logs" > "$LOG_DIR/logs.zip"
unzip -o "$LOG_DIR/logs.zip" -d "$LOG_DIR/extracted/"
ls "$LOG_DIR/extracted/"
ZIP を展開すると以下の構造が得られる(ワークフローにより構造は異なる。展開後に必ず ls で確認すること):
{N}_{job_name}.txt — job のフルログ(タイムスタンプ付き、N は 0 始まりの連番)
{job_name}/system.txt — job のシステムログ
{job_name}/{N}_{step_name}.txt — ステップ個別ログ(N は 1 始まり。存在しない場合もある)
ファイル名の {job_name} 部分は job 名そのもの。スペースや特殊文字を含む場合もそのまま使われる。ls でファイル一覧を確認し、失敗 job 名とファイル名を目視で対応づける。
ログの解析
function analyze_logs(run, log_dir):
// Step 1: run の job/step 構造を取得
jobs_info = gh("run view " + str(run.databaseId) + " --json jobs --jq '.jobs[]'")
failed_jobs = filter(jobs_info, conclusion not in ["success", "skipped"])
// Step 2: 失敗した job のログを読む
findings = []
for job in failed_jobs:
// ls で展開済みファイル一覧を確認し、job 名に一致するファイルを探す
log_file = match_log_file(log_dir, job.name)
// Read ツールでログファイル全文を読む(grep しない)
content = Read(log_file)
// Step 3: エラーマーカーから原因を特定
// ログ内のマーカー:
// ##[error] — エラー行(最優先)
// ##[warning] — 警告行
// ##[group] / ##[endgroup] — ステップ内の折りたたみセクション区切り
errors = find_lines_containing(content, "##[error]")
// Step 4: エラー行の前後コンテキストから根本原因を判断
error_analyses = []
for error in errors:
context = surrounding_lines(content, error.line_number, before=30, after=10)
error_analyses.append({error: error, root_cause: diagnose(context)})
findings.append({
job: job.name,
failed_step: identify_failed_step(content),
errors: error_analyses,
log_file: log_file
})
return {findings, has_fixable_issues: len(findings) > 0}
function report_findings_to_user(analysis):
// ユーザーへの報告形式:
// 1. 失敗した job 名とステップ名
// 2. エラーメッセージ(##[error] 行)
// 3. 根本原因の診断
// 4. 修正方針(コード変更が必要か、設定変更か、re-run で解決するか)
for finding in analysis.findings:
report_to_user(
"Job: " + finding.job,
"Failed step: " + finding.failed_step,
"Errors: " + finding.errors,
"Log file: " + finding.log_file
)
修正の適用
原因を特定したら、以下の方針で修正する:
- コードのバグ → 該当ファイルを修正し、push して CI を再実行
- 依存関係の問題 →
package.json / lock ファイルを更新
- 環境・設定の問題 →
.github/workflows/*.yml を修正
- 一時的なネットワークエラー等 →
gh run rerun {run_id} で再実行(コード修正不要)
- 原因不明 → ログの該当箇所をユーザーに提示し、判断を仰ぐ