Claude CodeでCIのテスト失敗を自動修正 — claude -pで修正PRを作る
CIで落ちたテストのログ末尾をclaude -pに渡し、修正をPRとして出すGitHub Actionsの構成です。dontAskとallowedToolsの権限、再実行で直ったかを確かめる関門まで書きます。
CIでテストが落ちたら、失敗ログの末尾だけを claude -p に渡して原因を直させ、修正は新しいブランチのPRとして出す。この流れはGitHub Actionsのワークフロー1本で組めます。要になるのは3点です。Claudeに渡す権限を絞ること、ワークフロー自身がテストを再実行して直ったかを判定すること、main へは直接pushしないことです。
Claude CodeをGitHub Actionsに組み込むではaction v1の導入を扱っています。ここでは「テストが落ちたときだけ動く修正ジョブ」の構成に絞ります。
失敗から修正PRまでの流れ
テスト失敗から修正PRまで
- 1
テストを実行してログを残す
npm testの出力をtest.logに書き出し、失敗してもジョブを止めません。 - 2
失敗時だけログ末尾を渡す
tail -n 150 test.logをclaude -pの標準入力に流します。 - 3
許可したツールだけで修正させる
読み取り、
src/配下の編集、テスト再実行の3つだけを許可します。 - 4
ワークフローがテストを再実行する
Claudeの「直りました」は信用せず、同じテストコマンドを自分で走らせます。
- 5
通ったときだけ修正PRを出す
新しいブランチへpushし、
main向けのPRを作ります。
claude -p は非対話で動くモードで、成功すれば終了コード0、失敗すれば非0で終わります。標準入力も読むので、ビルドログをパイプで渡す使い方は公式のheadless解説にもあります。
ワークフローの全体像
次のYAMLは、main へのpushでテストを回し、失敗したときだけ修正ジョブ相当のステップが動く構成の一例です。パスやコマンドはプロジェクトに合わせて読み替えます。
name: test-and-autofix
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- name: テスト
id: test
continue-on-error: true
run: |
set -o pipefail
npm test 2>&1 | tee test.log
- name: Claudeに修正させる
if: steps.test.outcome == 'failure'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
npm install -g @anthropic-ai/claude-code
tail -n 150 test.log | claude --bare -p \
"標準入力は失敗したテストのログ末尾です。原因をsrc配下で直し、npm testで確認してください。" \
--permission-mode dontAsk \
--allowedTools "Read" "Edit(src/**)" "Bash(npm test)" "Bash(npm test *)" \
--max-turns 15
- name: 直ったかを再実行で確かめる
id: verify
if: steps.test.outcome == 'failure'
continue-on-error: true
run: npm test
- name: 修正PRを作る
if: steps.verify.outcome == 'success'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name "autofix-bot"
git config user.email "autofix-bot@users.noreply.github.com"
BRANCH="autofix/${GITHUB_RUN_ID}"
git switch -c "$BRANCH"
git add -A -- src
git commit -m "fix: CIで失敗したテストの自動修正"
git push origin "$BRANCH"
gh pr create --base main --head "$BRANCH" \
--title "fix: テスト失敗の自動修正 (run ${GITHUB_RUN_ID})" \
--body "失敗ログ末尾をClaudeに渡して修正し、再実行で通過を確認しました。レビューしてからマージします。"
- name: 元のテスト失敗を赤のまま残す
if: steps.test.outcome == 'failure'
run: exit 1最後のステップは、修正PRができても元のコミットのCIを赤のまま残すためのものです。直したのはPRの中身であって、main のコミットは失敗したままだからです。
ログは末尾だけ渡す
標準入力には上限があり、公式は10MBとしています。超えると非0で終了するので、巨大なログを丸ごと渡す構成は避けます。末尾150行程度なら上限に当たる心配はなく、トークンも節約できます。
末尾を切るときは、テストランナーが失敗の要約を最後にまとめて出すかどうかを確認します。JestやPytestは最後に失敗一覧を出しますが、並列実行の出力が混ざるランナーでは、要約より前に原因が流れている場合があります。そのときは tail の行数を増やすか、失敗したテスト名で grep -A 30 して渡します。
渡すのは「ログ」と「やってほしいこと」の2つだけにします。ファイル名を推測させる指示は要りません。Claude自身が Read でソースを開けます。
権限は「許可した分だけ」にする
非対話の claude -p は、権限モードを指定しないとManualモードで始まります(Claude Code v2.1.302以降)。承認が必要な呼び出しは、許可ルールやフックが無ければ拒否されます。無人のCIでは都合のよい既定ですが、何も許可しなければ何も直せません。
CIで使える設定は主に3つあります。
| 設定 | 動き | このジョブでの扱い |
|---|---|---|
--permission-mode dontAsk + --allowedTools | 動き許可リストにある呼び出しだけ実行し、残りは拒否 | このジョブでの扱い採用。許可の範囲が一目で分かる |
--permission-mode auto | 動き分類器が各操作を審査する | このジョブでの扱い許可リストを書かずに済むが、通る操作が読みにくい |
--dangerously-skip-permissions | 動き確認を全て省く | このジョブでの扱いコンテナなど隔離環境が前提。この用途では避ける |
dontAsk は、承認が必要な呼び出しをすべて拒否するモードです。読み取り専用のコマンドなど、承認不要のものは動きます。--allowedTools に挙げた分も動くので、許可リストがそのまま権限の全体になります。
許可リストの書き方で効く点が3つあります。
Edit(src/**)で編集範囲を絞る: パスを付けたEditルールは、その配下だけに効きます。テストファイルや.github/を許可しなければ、Claudeがテスト側を書き換えて通す近道を塞げますBashは裸で許可しない:Bash(npm test)のようにコマンド単位で書きます。末尾に空白と*を付けたBash(npm test *)は前方一致で、npm testの後に引数が付く呼び出しを通しますgitは許可しない: コミットとpushは後続のステップに任せ、Claudeの権限には含めません
--max-turns でターン数の上限も付けます。上限に達すると非0で終了し、後続の再実行ステップが走らないのでPRは出ません。
「直ったか」はワークフロー側で確かめる
Claudeが npm test を実行して通ったと報告しても、その結果は次のどちらかです。本当に直ったか、Claudeが見た環境でたまたま通ったかです。そこで、claude のステップとは別に、同じコマンドをワークフローが再実行します。
上のYAMLでは、再実行ステップの結果(steps.verify.outcome)が success のときだけPR作成に進みます。失敗すればPRは出ず、ログに失敗が残るだけです。
もう1つの関門が、変更の範囲です。git add -A -- src で src/ だけをステージするので、Claudeが許可外のファイルを何かの経路で変えても、コミットには含まれません。差分が空だと git commit が失敗してジョブが止まります。「直す対象が無かった」ことが赤いステップとして見えます。
mainに入れず、PRで人に渡す
修正はブランチ autofix/<run id> にpushし、main 向けのPRとして出します。マージは人間が決めるので、Claudeの修正が誤っていても main は汚れません。ワークフローに必要なのは contents: write と pull-requests: write で、公式のGitHub Actionsの例も同じ権限を使っています。
落とし穴
GITHUB_TOKENで作ったPRではCIが走らない
GitHubは、既定の GITHUB_TOKEN で行ったコミットからはワークフローを起動しません。公式のトラブルシューティングにも同じ注意があります。この構成で出した修正PRには、テストのワークフローが自動では走らない点に注意します。PRにもCIを走らせたいときは、GitHub Appのトークンなど別の認証を使います。このジョブはPR作成の前に自前で再実行しているので、最低限の確認は済んでいます。
自動修正が自動修正を呼ぶループにも注意が要ります。修正PRのマージ後にまた main で失敗すると、次の修正PRが出ます。最初は workflow_dispatch で手動起動だけにして、挙動を見てから push トリガーを足すと安全です。
action v1で組むときの違い
同じ流れは anthropics/claude-code-action@v1 でも組めます。prompt を渡すと自動実行モードになり、コメントのメンションを待たず動きます。権限は claude_args に --allowedTools として書きます。
ただし、自動実行モードのアクションは、起動したユーザーが人間かどうかも検査します。ボットが起動した実行は、allowed_bots に載せない限り拒否されます。schedule 起動の実行も、GitHubが最後に cron を編集したユーザーの実行として扱うため、その人がボットなら対象です。
| 観点 | claude -p を直接呼ぶ | claude-code-action@v1 |
|---|---|---|
| 認証 | claude -p を直接呼ぶANTHROPIC_API_KEY を環境変数で渡す | claude-code-action@v1anthropic_api_key かOAuthトークン、GitHub App |
| 権限の指定 | claude -p を直接呼ぶCLI引数 | claude-code-action@v1claude_args 内のCLI引数 |
| PR作成 | claude -p を直接呼ぶワークフローが gh で行う | claude-code-action@v1Claudeに任せる設計(Claude側にGitHubの書き込み手段が要る) |
| ボット起動 | claude -p を直接呼ぶ制限なし | claude-code-action@v1allowed_bots で許可が必要 |
細かい違いはGitHub Actionsのパラメータとclaude_argsの渡し方にまとめています。この記事のように「渡す権限をワークフローの外側で固定したい」ジョブでは、直接呼ぶ構成のほうが何が起きるか読み取りやすくなります。
プロジェクトの規約は--bareでは読まれない
公式はCIでは --bare を勧めています。ユーザーごとの ~/.claude や .mcp.json に左右されず、どのマシンでも同じ結果になるからです。ただし --bare はCLAUDE.mdも自動では読みません。「テストは変えない」「依存を足さない」といったリポジトリの規約をCLAUDE.mdに書いていても、このジョブには届かない点に注意します。
規約を渡すには、--append-system-prompt-file でファイルを指定します。CI専用の短い規約ファイルを用意するのが扱いやすい形です。
# .github/autofix-rules.md
- 修正は src/ 配下だけで行う
- テストの期待値を変えて通さない。実装側を直す
- 依存パッケージを追加しない
- 原因が分からないときは変更せず、その旨だけ書いて終了する tail -n 150 test.log | claude --bare -p \
"標準入力は失敗したテストのログ末尾です。直してnpm testで確認してください。" \
--append-system-prompt-file .github/autofix-rules.md \
--permission-mode dontAsk \
--allowedTools "Read" "Edit(src/**)" "Bash(npm test)" "Bash(npm test *)"規約の最後の行は、直せないときの出口です。出口が無いと、通すために無関係な箇所を触りに行く方向へ流れやすくなります。許可リストで塞ぐ壁と、規約で示す方針の両方を置く形です。
この構成に向く失敗と向かない失敗
どんな失敗でも自動修正に向くわけではありません。ログ末尾を渡す設計は、原因がログに写っている失敗に合います。
| 失敗の種類 | 向き不向き | 理由 |
|---|---|---|
| 型エラー、未定義の変数、import漏れ | 向き不向き向く | 理由スタックトレースに場所が出る |
| 直前のコミットで壊れた単体テスト | 向き不向き向く | 理由差分が小さく、原因が src/ 内に収まる |
| 実行のたびに結果が変わる不安定なテスト | 向き不向き向かない | 理由再実行で通るだけの「修正」がPRに混ざる |
| 外部サービスや環境変数に依存する失敗 | 向き不向き向かない | 理由直す場所が src/ の外にあり、編集範囲の許可外になる |
不安定なテストは、ワークフロー側の再実行ステップがかえって誤判定を招きます。直していないのに2回目で通れば、PRが出てしまうからです。再実行を2回続けて通すことを条件にするか、不安定なテストは別の対処に回すのが現実的です。
よくあるつまずき
動かないときの確認先
認証エラーになる
--bareではOAuthの資格情報やキーチェーンを読みません。APIキーを使うなら環境変数ANTHROPIC_API_KEYに入れ、ステップのenv:で渡します。何も直らず終わる
許可リストが足りず、
Editやnpm testが拒否されていることが多いです。--output-format stream-jsonを付けると、拒否された呼び出しがpermission_deniedとして出ます。forkのPRで動かない
公開リポジトリでは、fork元のPRにはsecretsが渡りません。この構成を
pull_requestで回すなら、同一リポジトリのブランチのPRに限る前提になります。費用が読めない
--max-turnsとtimeout-minutesで上限を付けます。--output-format jsonにすると、応答にtotal_cost_usdが入ります。
autofix-prとの使い分け
Claude Code自身にも、PRのCI失敗を監視して自動修正する組込みの機能があります。仕組みと動かない原因はClaude Code autofix-prとはに分けて書いています。
| 観点 | 組込みのauto-fix | この記事のワークフロー |
|---|---|---|
| 起動 | 組込みのauto-fixPRごとに有効化する | この記事のワークフローワークフローのトリガーで決める |
| 対象 | 組込みのauto-fixPRのCI失敗とレビューコメント | この記事のワークフロー自分が決めたテストコマンドの失敗 |
| 権限と手順 | 組込みのauto-fix製品側が決める | この記事のワークフローYAMLで全て固定できる |
| 向く場面 | 組込みのauto-fixPRの面倒を任せたい | この記事のワークフロー手順と権限を監査可能にしたい |
テストを書く側の進め方はClaude Codeでテスト駆動開発を回す手順にあります。
まとめ
失敗したテストの自動修正は、Claudeに任せる範囲を「ログ末尾を読み、src/ を直し、テストを回す」までに限ると組みやすくなります。コミット、push、PR作成、そして直ったかの最終判定はワークフローの側に置きます。こうすると、Claudeの判断が誤っても、損なわれるのは新しいブランチのPRだけです。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。