Claude Code Action と Copilot レビューで GitHub Actions の失敗を自己修復するパイプラインを作った話
OSS の上流を追いかけながら日本語化したフォークを、できれば人手をかけずに継続リリースしたいですよね。とはいえメンテに張り付くと続かないので、「Actions が失敗したら AI に直させる、PR は Copilot に審査させる」という二重ループの自己修復パイプラインを組んでみました。本記事では擬似プロジェクト Aurora i18n Sync を題材に、構成要素と踏み抜いた地雷をまとめていきます。
前提
このプロジェクトは「個人アカウントが個人のために運用している OSS フォーク」を想定した構成です。Claude や Copilot に PR の書き換えまで自動でやらせる仕組みになっているため、組織のリポジトリで真似する場合は事前にちゃんと許可を取ってからにしてください。詳しくは記事末尾の「注意 ― 会社で同じことをやるなら」にまとめています。
TL;DR
- 上流リリースを 1 時間おきに監視 → 差分翻訳 → 自動 PR、までは普通の bot です。
- 失敗したワークフローを
workflow_runトリガーで拾い、anthropics/claude-code-actionに原因診断と修復 PR の作成までやらせます。 - 修復 PR は GitHub Copilot に審査させ、
VERDICT: APPROVEなら squash マージ、REQUEST_CHANGESなら同じ Claude Code Action で diff を機械修正して push し直します。 - Branch protection の必須ステータスチェック名・許可ブランチプレフィックス・ラベルでガードを掛けて、暴走を抑えます。
- PR 合戦で大量のメール通知が飛ぶので、メールは黙らせて エスカレーション時だけ Slack 通知 に逃がします。
想定プロジェクト ― Aurora i18n Sync
「Aurora」という OSS の上流リポジトリを定期的に取り込み、UI 文言の日本語版ロケール ja.json を当てた Docker イメージを公開する、というフォーク運用を想定します。実体は以下の 5 つの GitHub Actions ワークフローで構成しています。
| ワークフロー | トリガー | 役割 |
|---|---|---|
| upstream-monitor | schedule (cron) | 上流リリースを取得、差分翻訳、自動 PR を作成 |
| pr-validate | pull_request | YAML lint / format / スモークテストを走らせる |
| release-ja | push (main) | タグ付与、Docker イメージビルド・公開 |
| auto-heal | workflow_run | 他ワークフローの失敗を診断、修復 PR を起票 |
| copilot-review-handler | issue_comment | Copilot レビュー結果を解釈してマージ or 自動修正 |
人間のオペレータが担当するのは「最終マージの目視確認」と「Copilot が放棄した PR の救出」だけ、というのが理想形です。
全体の流れを図にするとこんな感じです。
実線が正常系の流れ、点線が「コメント駆動」「失敗検知」のような副次的なフローです。auto-heal と copilot-review-handler が回り続ける限り、放っておけば自然と main に最新版が降ってくる、というのが目指している姿です。
なぜ Jenkins を挟むのか ― Copilot 公式レビューを使えない事情
最初に詰まった部分の話をさせてください。このプロジェクトのリポジトリオーナーが普段ログインしているアカウントは GitHub Copilot Free プラン です。Free だと、Pro / Pro+ / Business で使える PR レビュー (Copilot code review) や Copilot Coding Agent が、Copilot 公式 Bot として GitHub の PR に貼り付かないんですよね。
ただし、別途 Copilot Pro+ で契約済みのアカウント の credentials が手元にあるので、その PAT を使った gh copilot CLI 経由ならプロンプトを Copilot に投げられます。これを使って「公式の Copilot review Bot ではないけれど、Pro+ のレビュー能力を CLI から呼び出す」構成にしました。具体的には、
- 自前の Jenkins ジョブで PR の diff を取得
- Pro+ アカウントの認証情報を Jenkins の Credentials に置き、シェル上で
gh copilot suggest系のコマンドにレビュープロンプトを流し込む - 出てきたレビュー結果を PR コメント として GitHub に書き戻す
という流れで、Copilot のレビュー機能を「外から」貼り付ける構成にしました。GitHub Actions ではなく Jenkins を使っているのは、gh copilot の認証に使う PAT を Actions のシークレットではなく Jenkins の Credentials に置いておきたい、というセキュリティ境界の都合もあります (Actions 内で gh copilot を呼ぶと、フォーク元の任意ユーザが PR を介して間接的に発火できる経路を作りやすいので、外部ジョブで隔離しています)。
その代わり、Jenkins から GitHub に投げ込まれたコメントを GitHub Actions 側がコメントとして解釈 しないといけません。これが後述の copilot-review-handler.yml の仕事です。
なお、執筆時点の GitHub Copilot は新規契約の受付が一部制限されているタイミングです。Copilot の新規契約が再開 されて Free アカウント側からも公式 PR review が直接呼べる仕様になったり、逆に CLI 経由のレビューが将来の仕様変更で立ち行かなくなったタイミングでは、Jenkins を介さずに 正規の Copilot Code Review を使う方向で再検討する つもりです。今の構成は、あくまで現状の制約下での回避策というポジショニングです。
権限境界をシーケンス図にするとこうなります。
gh copilot を叩く PAT は Jenkins の Credentials 側だけに置き、Actions の secrets には入れません。これで「Actions の YAML を改ざんしても Copilot 認証情報は抜けない」状態になります。
ピース 1 ― 上流追従と差分翻訳 PR
最初のループはよくある上流追従ボットですが、ここで設計を雑にすると後段の自己修復が成立しなくなります。決め事は次の 3 つです。
-
追跡対象の上流バージョンを
.upstream-versionファイル 1 つに集約します。差分検知もrelease-jaの入力も、すべてこのファイルを起点にします。 -
上流タグの取得は jq の正規表現で 意図したメジャー系列だけ に絞ります。たとえば 2.x 系のみを追いたいなら下記のような書き方になります。
tag=$(gh_api "https://api.github.com/repos/upstream/aurora/releases?per_page=100" \ | jq -r '[.[] | .tag_name | select(test("^aurora@2\\.[0-9]+\\.[0-9]+$"))][0]') -
翻訳対象は「追加・変更のあったキーだけ」を抽出した worklist にします。全文翻訳は遅いですし、無駄に diff が膨らんで Copilot レビューも崩壊します。jq の
paths(scalars) + tojsonでパスを辞書キー化する流儀については別記事で詳しく書きました。
ここまでが既存の bot 構成です。問題はこれが 壊れたとき に誰が直すか、という点ですよね。
ピース 2 ― workflow_run と Claude Code Action による自動診断
GitHub Actions には「他ワークフローの実行完了をトリガーにできる」workflow_run イベントがあります。これに claude-code-action を組み合わせると、失敗ランを AI に丸投げできます。
name: 失敗ワークフローの自動診断・修復
"on":
workflow_run:
workflows:
- upstream-monitor
- release-ja
types: [completed]
permissions:
contents: write
pull-requests: write
issues: write
actions: read
jobs:
diagnose-and-fix:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 0 }
- name: Diagnose failure and open fix PR (Claude Code)
uses: anthropics/claude-code-action@v1
env:
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.RELEASE_PAT }}
prompt: |
失敗ラン: ${{ github.event.workflow_run.html_url }}
手順:
1. gh run view <id> --log-failed で根本原因を特定する
2. 既存 auto-heal PR/Issue があれば重複させず終了する
3. 機械的に修正可能ならブランチ auto-heal/<run_id> を切って PR
4. 外部要因なら issue で起票する
制約: ロケールデータと main へは触らない
claude_args: |
--max-turns 80
--allowedTools "Read,Write,Edit,Glob,Grep,Bash(gh:*),Bash(git:*),Bash(jq:*)"
ポイントは次の通りです。
-
PAT を分ける ―
GITHUB_TOKENだと自分が起こしたイベントに別のワークフローが反応してくれません。RELEASE_PATのような専用 PAT をghとclaude-code-actionのgithub_token両方に渡します。 -
触らせる範囲を絞る ―
--allowedToolsでBash(...)を コマンド単位 にホワイトリスト指定します。Bash(*)は事故のもとです。 -
重複防止 ― 同じ
run_idを表すauto-heal/<run_id>ブランチが既にあるなら、新規 PR を作らせません。プロンプト内で明示し、Claude にもgh pr list --label auto-heal --state openで確認させます。 - 外部要因なら issue へ ― ネットワーク・上流障害・シークレット未設定など、コード修正で直らない種類の失敗は無理に PR を作らせず Issue 化します。失敗の 分類 を AI に任せるかが運用の善し悪しを分けます。
このワークフローを入れただけで、ふだん人間が「gh run view してログを読んで、原因を当てて、ブランチ切って、最小修正してプッシュ」していた作業の 8 割が自動化されました。
意思決定の枝分かれを図にするとこんな感じです。
「修正できるか」の判定を AI に任せきりにせず、Issue への退避口を必ず用意しておくのが、暴走させないコツでした。
ピース 3 ― Copilot レビューから自動マージ・自動修正へつなぐループ
自己修復 PR をそのまま auto-merge にしてしまうと、Claude が読み違えた修正がそのまま main に入ってしまいます。そこで先ほど話した Jenkins 越しの Copilot レビュー結果を仲介させます。
copilot-review-handler.yml のおおまかな流れは次の通りです。
- Jenkins ジョブが PR の diff を
gh copilotに流して、レビュー結果を PR コメントとして書き戻します。本文末尾にVERDICT: APPROVEかVERDICT: REQUEST_CHANGESの 1 行を 厳格マッチ で書かせておくのがコツです。 - GitHub Actions のワークフローがコメント本文を grep して VERDICT を抽出します。
- PR メタデータでゲート判定します。条件は次のとおりです。
- 状態が
open - head リポジトリが同一リポジトリ
- ラベルが
auto-heal/automated/aurora-updateのいずれか - ブランチ名が
auto-heal/*またはauto/aurora-*-ja
- 状態が
-
APPROVEならgh pr merge --squash --delete-branch --autoで merge します。 -
REQUEST_CHANGESなら、再びclaude-code-actionを呼び出してコメント本文を入力に diff を機械修正し、push し直して PR のsynchronizeイベントで Jenkins 再レビューを誘発します。 - ループ回数を Jenkins のレビューコメント数でカウントし、
MAX_RETRYを超えたらneeds-human-reviewラベルを付けて停止します。
VERDICT を「厳格マッチ」にするのは大事で、VERDICT: APPROVE. のようにピリオドが付いた瞬間に正規表現が落ちます。逆に言えば、その正規表現を contract として固定すれば、Copilot 側のテンプレートを少し変えるだけで運用ポリシーを差し替えられます。
VERDICT=$(printf '%s' "$BODY" \
| tr -d '\r' \
| grep -oE '^VERDICT: (APPROVE|REQUEST_CHANGES)$' \
| tail -1 \
| awk '{print $2}')
VERDICT 駆動のループ全体はこんなイメージです。
MAX_RETRY を入れておかないと、Claude と Copilot がお互いに気に入らない修正を投げ合って永久ループするので必須です。なお、いま使っている Copilot 接続は前述のとおり Pro+ アカウントを gh copilot 経由で借りる 構成です。GitHub Copilot 側の新規契約が再開されて Free アカウントからも公式の PR review が呼べるようになったり、CLI 側の仕様が変わって今のやり方が立ち行かなくなったタイミングで、Jenkins 経由のレビューを畳んで 正規の Copilot Code Review に乗り換えるか を改めて検討する予定です。
ピース 4 ― PR 提出前のローカルセルフレビュー (上流フィルタ)
ピース 3 で組んだ Copilot ↔ Claude のレビューループはよく動きますが、何度も回せば回すほど API 利用が嵩み、メール通知も増えます。「そもそもループに入る前に潰せる指摘は潰しておこう」ということで、PR を出す前のローカル側にも自己レビューフェーズを挟みました。
Claude Code 2.1.x 系で提供されている /code-review --fix スキルを開発機で叩くと、現在の diff (working tree 含む) を Claude が読み、検出した cleanup 候補を working tree に直接適用 してくれます。位置付けは「自分の git push の手前で diff を一度なめる」というシンプルなものです。
推奨フローはこんな感じです。
- 作業ブランチで編集して WIP コミットを重ねる。
-
/code-review highで確認だけ走らせるか、/code-review --fixで低リスクの cleanup を適用する。 -
git diffで適用された差分を 必ず目視確認 し、不要な変更は revert する。 - 1 コミットに squash → push → PR を作成。
- push 後に追加レビューが欲しい場合は
/code-review --commentで PR コメントとして投稿します。深いレビューが必要なときは/code-review ultra <PR#>でクラウド多エージェントレビュー (課金あり) も呼べます。
これは Jenkins 経由の Copilot レビュー (ピース 3) や claude-code-action の auto-fix (ピース 2) の 代わりにはならない 点だけ注意してください。あくまで「PR に乗る前段で潰せる指摘を潰しておく」ための上流フィルタです。決定論的な YAML lint / format check / smoke test は CI 側に残しっぱなしですし、Jenkins レビューや auto-heal ループも素通りさせません。
レイヤを図にするとこうなります。
青く塗ってあるところが今回足したローカルフェーズです。CI 側の決定論的ゲートと AI 駆動レビューループは丸ごと残してあります。
実際の体感としては、/code-review --fix で let → const、未使用 import 削除、== → ===、文字列連結 → テンプレートリテラルなどの軽い cleanup を 5〜10 件まとめて消化してくれるので、Copilot レビューのループ回数が体感で 1〜2 回減る印象です。「PR に出してから直す」のは精神衛生的にもよろしくないので、push 前に一回なめておくだけで運用が楽になりました。
注意点としては、/code-review --fix は Claude モデルによる確率的な判断 なので、git diff で結果を必ず確認することです。とくに翻訳ファイル languages/** のような「機械翻訳フロー専用」ファイルは適用対象から除外する運用にしておいた方が安全です (もし適用されたら revert する)。Claude モデル側に「ここは触らないでね」という指示を CLAUDE.md に書いておくと事故が減ります。
暴走防止の 2 層ガード ― Branch protection と eligibility gate
自動マージや自動修正ループを暴走させないために、Aurora i18n Sync では GitHub 側の Branch protection と copilot-review-handler.yml 内の eligibility gate という 2 つのガードを重ねています。前者はサーバ側で必須 status check の通過を強制し、後者はアプリ側で「どの PR を自動マージ・自動修正の対象にしてよいか」を判定します。役割が違うので、混同したり片方だけで済ませようとすると事故ります。
Branch protection 側 (サーバが弾く)
main の ruleset に必須 status check を 3 つ登録しています。
| 必須 check | 何を満たすか |
|---|---|
yaml-workflow-lint |
actionlint と yamllint |
format-check |
jq / json formatter |
smoke-test |
フォーク版が起動して /healthz が 200 を返す |
これらが全部 green にならない限り、gh pr merge --auto をいくら叩いても GitHub 側でマージが弾かれます。決定論的な CI ゲートで、ここを通れないものはそもそも main に到達しません。
eligibility gate 側 (handler が弾く)
copilot-review-handler.yml の triage ジョブは Jenkins からの VERDICT コメントを受け取った後、PR メタデータをチェックして「この PR は自動処理対象か」を判定します。条件は以下を AND で全て満たす必要があります (OR ではありません)。
- 状態が
open - head リポジトリが同一リポジトリ
- 以下のいずれかのラベルが付与されている:
auto-heal/automated/aurora-update - ブランチ名が
auto-heal/*またはauto/aurora-*-jaのいずれか
ラベルとブランチ名の両方を契約として固定しておくのがミソで、「automated ラベルだけ付ければ自動修正が走る」と読み間違えると、テスト用に切った test/foo ブランチで Jenkins レビューは来るのに auto-fix ジョブが起動しない、という不思議な現象でハマります (実体験です)。両方揃ったときだけ自動処理を許可することで、うっかり手元のブランチが自動マージされる事故を防いでいます。
名前を変えるときの注意
yaml-workflow-lint / format-check / smoke-test のような必須 check 名や auto-heal/* のブランチプレフィックス命名は、Skills.md のような正規ソースにも書いておきます。「ある日 main にマージできなくなった」「auto-merge が突然動かなくなった」の犯人はだいたいこの名前のドリフトです。サーバ側ガードとアプリ側ガードの両方を書き換える必要があるので、変更時は両方同期させるルールにしておくのが安全です。
ハマったところ
実運用してみて踏んだ罠を 4 つほど紹介します。
LLM 出力の 32k トークン上限とタイムアウトの二段ヒット
claude-code-action をそのまま使って巨大な to_translate.json を投げると、
- 単一応答で 32,000 トークン出力上限を超えてエラー。
- 表示されるエラーは
API Error: Claude's response exceeded the 32000 output token maximum.
- 表示されるエラーは
-
CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000まで上げると今度は応答生成が 30 分のステップタイムアウトを突破します。- 表示されるエラーは
Error: The action has timed out.
- 表示されるエラーは
解決はチャンク分割です。jq で 40 キーずつの小さな入力ファイルに割って、claude --print --permission-mode acceptEdits --allowedTools "Read,Write,Edit" --max-turns 10 を逐次呼び出します。1 チャンクあたりの出力は数千トークンに収まるので、再試行や検証も粒度が細かくできて結果的に安定しました。
for ((start=0; start<total; start+=CHUNK_SIZE)); do
end=$((start + CHUNK_SIZE))
jq --argjson s "$start" --argjson e "$end" '
to_entries | .[$s:$e] | from_entries
' "$src" > "$(printf "%s/in-%03d.json" "$chunks_dir" $((start / CHUNK_SIZE)))"
done
各チャンクの出力は キー集合の完全一致 で検証します。キー数だけ比較するとリネームを見落とすので、最初これでハマりました。
jq -e --slurpfile ref "$in_file" '
type == "object"
and (keys_unsorted | sort) == ($ref[0] | keys_unsorted | sort)
' "$out_file" > /dev/null
curl | bash 依存をやめる
最初は Claude Code CLI を curl -fsSL https://claude.ai/install.sh | bash で入れていたのですが、レビュアー指摘を受けて npm install -g @anthropic-ai/claude-code@2.1.150 に差し替えました。理由は次の通りです。
- バージョンピン留めができる。
- 改ざん検知が npm registry のメタデータに乗る。
- インストール直後に
claude --versionでセルフテストできる。
「動けば良い」コードでも、自己修復ループの中で動く以上 supply chain attack のリスクを正面から考えておかないと、ある日全 PR が悪意ある修正を運んでくることになります。怖いです。
gh pr merge は --auto 必須
Branch protection を有効にしている前提だと、gh pr merge --squash を即時実行しても check 未完了で弾かれます。--auto を付ければ check が緑になり次第まとめてマージしてくれます。緊急時に --admin で押し込みたい誘惑はありますが、必須 check や承認をスキップしてしまうリスクが高いので、auto-heal PR では基本使いません。
PR 合戦でメールが地獄になる ― Slack に逃がす
これは技術的な罠というより 感想 に近いんですが、Claude が修正 push → Copilot が再レビュー → また Claude が修正 push、というループが回り始めると、1 つの PR から 5〜10 通のメール通知 が矢継ぎ早に届きます。リポジトリオーナーが自分ひとりだと、自分の Inbox が一番先にレートリミットされて、見るのが嫌になります。これは想像以上にきつかったです。
対策として、GitHub のメール通知は Web 通知側に黙らせて、本当に人間の介入が必要なときだけ Slack に Incoming Webhook で 1 行投げるようにしました。発火条件を絞ることが大事です。
-
auto-healが「外部要因」と判断して Issue に逃がしたケース - Copilot レビューのループが
MAX_RETRYを超えてneeds-human-reviewに落ちた PR -
release-jaが main マージ後に Docker push まで進めず失敗したケース
逆に「Claude が修正 push して Copilot が APPROVE して自動マージ」のような 正常系のループ は一切通知しません。実装は curl 一発で済みます。
- name: Notify Slack on escalation
if: failure() || steps.gate.outputs.needs_human == 'true'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
target_url="${{ github.event.pull_request.html_url || github.event.workflow_run.html_url }}"
reason="${{ steps.gate.outputs.reason || 'workflow failed' }}"
payload=$(jq -n --arg url "$target_url" --arg reason "$reason" '
{text: ("🚨 Aurora i18n Sync escalation\n<" + $url + "|PR/Run>: " + $reason)}')
curl -fsS -X POST -H 'Content-Type: application/json' \
-d "$payload" "$SLACK_WEBHOOK_URL"
これで「普段は静か、本当に必要なときだけ Slack で 1 行アラート」という運用になりました。メールクライアントを開かなくてよくなっただけで体験がだいぶ違います。
注意 ― 会社で同じことをやるなら
ここまでに紹介した構成は、繰り返しになりますが 個人アカウントの個人 OSS で、自分のリポジトリ・自分のシークレット・自分の API 利用枠を相手にやっている前提のものです。同じことを会社や組織のリポジトリでやろうとすると、想像以上に踏むべき手続きがあります。先に通しておかないと、あとで監査や情報セキュリティ部門から「いつの間にか AI が本番ブランチを書き換えていた」とひっくり返されがちです。
最低限、以下のあたりはチームやセキュリティ担当に確認してから着手するのをおすすめします。
- AI 連携の社内ポリシー ― Anthropic / OpenAI / GitHub Copilot に業務コードを送ってよいか、契約・規程レベルで OK が出ているか。商用利用条件・データ学習扱いの確認も込みで。
-
PAT と GitHub App の権限範囲 ―
RELEASE_PATのような長命トークンを誰が発行し、誰が棚卸しするか。Administration: writeを含む fine-grained PAT は特に管理が必要です。 -
Branch protection と admin bypass の運用 ― 必須 check をスキップできる admin 権限を bot 経由で使う設計になっていないか。
gh pr merge --adminは便利ですが、組織用途では原則禁止にした方が安全です。 - シークレットの取り扱い ― CI/CD ベンダー (GitHub Actions, Jenkins ほか) を跨いで PAT や OAuth トークンを置く設計になっている場合、それぞれの保管場所・アクセスログ・ローテーション手順を文書化しておきます。
- Auto-merge のスコープ ― 何のラベル・どのブランチプレフィックスにだけ AI が自動マージできるかをチームで合意し、ドキュメント化します。曖昧にすると「あの PR、いつの間にか main に乗ってましたよ」が必ず起きます。
- 監査ログとレビュー記録 ― Copilot/Claude のレビュー結果や修正 diff を後追いできるよう、PR コメントや Jenkins ジョブ履歴を保存しておきます。「AI に直させた」だけで証跡が残らないと、後でレビュー責任の所在が曖昧になります。
要するに「個人で動くからって会社にそのまま持ち込まないでね」というだけの話なんですが、楽しい自動化は黙って入れたくなるので念のため書いておきます。
まとめ
- 上流追従 + 自動翻訳の bot に 失敗ハンドラの自動化 を足すと、人手の張り付き時間が劇的に減ります。
- Copilot 公式 PR review が使えない環境でも、Pro+ アカウントを Jenkins から
gh copilotで呼んで PR コメントに書き戻せばレビュー Bot 相当の役割を果たせます。新規契約や仕様変更のタイミングで正規の Copilot Code Review に乗り換えるかは、その都度見直すのが現実的です。 - AI に修復させるなら、ラベル・ブランチ名・必須 check の 3 つを 契約 として明文化しておくのが大事です。これがないと暴走を止められません。
- 巨大な LLM 入出力は素直にチャンクへ。検証はキー集合まで踏み込みましょう。
- 自動ループはメール通知を爆発させるので、エスカレーション時だけ Slack に逃がして、普段は完全に静かにしておくのがおすすめです。
完全無人ではなく「人が最後の砦」を残したままにしておくのが、現実的な妥協点だと思います。次は Copilot 以外のレビュアー (ローカル LLM 等) も差し替えられる形に整えていきたいです。
未経験から学べます!一緒に挑戦していきましょう![]()
noteもやってます↓