GitHub Actions無人実行でClaude Codeのサブエージェント委譲がジョブを止めていた話

概要

今回はGitHub Actions上でClaude Codeを無人実行するパイプラインで発生したサブエージェントへの委譲がジョブの完了処理を止めてしまう不具合について紹介していきます。

普段から、GitHub Issueに調査してほしいテーマを書いておくと、Claude Codeが必要な情報を自動で収集し、調査結果をMarkdownにまとめてdraft PRを作るところまで無人でやってくれる仕組みを運用しています。
しかしその途中に「レビュー用のサブエージェントに一度意見を聞く」というステップを入れており、これが原因でジョブは成功表示なのに成果物が作られないという現象が起きていました。

同じ手順なのに起きるときと起きないときがあって、最初は原因のわかりませんでした(- -;
詳細ログを追ってようやく分かった根本原因と、自然言語の指示強化では直せなかった理由、最終的にどう構造を変えたかを紹介します。

それではやっていきましょう!

目次

前提となるシステム構成

GitHub Issueに調査してほしいテーマを書いて特定のラベルを付けると、GitHub Actions上でanthropics/claude-code-actionを使ってClaude Codeが無人で起動する仕組みを使っています。

ワークフローがやることは、大きく分けて次の4つです。

  • Issueの内容を読み取る
    • タイトル・本文・ラベルをAPIで取得
  • 成果物(調査結果Markdownなど)を生成してコミットする
    • ブランチを切ってファイルを追加
  • draft PRを発行する
    • gh pr createでレビュー待ち状態のPRを作る
  • Issueに完了コメントを付ける
    • 作業内容とPRへのリンクを書き込む

これを1回のジョブで完結させる設計になっています。
さらに、成果物のドラフトが完成した段階で、コミット前に「レビュー用のサブエージェント」にレビューを依頼し、指摘のうち妥当なものだけを自分で反映してからコミットする、という手順を踏んでました。

この一連の手順は、Claude Codeの「スラッシュコマンド」としてMarkdownファイルに自然言語で記述してあり、GitHub Actionsからはprompt: "/xxx REPO: ... ISSUE_NUMBER: ..."という形でこのコマンドを呼び出しています。

発生した症状

ワークフロー自体はGitHub Actions上で「成功」表示になります。
にもかかわらず、PRやIssueコメントが作られないことがありました。

厄介だったのは、常に失敗するわけではなく定期的に発生することです。
同じIssue・同じ手順で試しても、成功する回と失敗する回がある状態でした。

Actions上のステータスだけ見ていると正常終了なので、訳がわかりませんでした(^^;

調査

まずワークフローYAMLのclaude-code-actionステップにshow_full_output: trueを追加し、Claude Codeの詳細な実行ログをActionsのログに出力させるようにしました。

workflow.yml

1
2
3
4
- uses: anthropics/claude-code-action@v1
with:
prompt: "/issue-to-research REPO: ${{ github.repository }} ISSUE_NUMBER: ${{ github.event.issue.number }}"
show_full_output: true

再発時のログを確認したところ、ジョブ内のClaude Codeセッションの最終出力(result)が次のような内容になっていました。

再発時のログ抜粋

1
2
3
"result": "レビューエージェントの完了を待ちます。完了次第、通知が届きます。"
"terminal_reason": "completed"
"subtype": "success"

これは「サブエージェントをバックグラウンドで起動し、完了通知を待つ」という状態でセッション(ターン)が正常終了していることを意味していました。

サブエージェント呼び出しがフォアグラウンド/バックグラウンドどちらで行われたかで結末が変わることを示すフローチャート
サブエージェント呼び出しの分岐と結末を示すフロー図。フォアグラウンド呼び出しならcommitからPR発行・完了コメントまで実行される一方、バックグラウンド呼び出しだとターンがcompletedで終了し後続処理が実行されない。しかもジョブ自体はgreenのまま終了するため見た目には分からない

根本原因

Claude Codeのサブエージェント呼び出し(AgentツールやTaskツール)は、デフォルトでバックグラウンド実行になります。
対話的なセッションでは、バックグラウンドで起動したサブエージェントが完了すると、その通知が次のターンとして会話に差し込まれ、メインのエージェントが処理を再開できます。

ただ、GitHub Actions上のclaude-code-actionによるセッションは、1回のAPI呼び出しで完結する使い捨てセッションです。
メインのエージェントが「サブエージェントの完了を待つ」という発話をして処理を止めた時点で、そのターンは「完了(completed)」と見なされ、ジョブ自体が終了してしまいます。
サブエージェントが後から完了しても、それを受け取ってジョブを再開する後続ターンは存在しません。

結果として、次のようなことが起きていました。

  • サブエージェントへの委譲がバックグラウンドで行われた回
    • レビュー以降の手順(コミット・PR発行・完了コメント)が一切実行されないまま、ジョブ自体は「成功」扱いで終わる
  • たまたまフォアグラウンド的に(完了を待ってから応答する形で)行われた回
    • 最後まで正常に完了する

これが「同じワークフローなのに定期的に発生する」という症状の正体でした。

対応1: 指示文の強化(不十分だった)

最初に試したのは、スラッシュコマンドのMarkdown(レビュー依頼の手順が書かれた箇所)に「このサブエージェント呼び出しは必ずフォアグラウンドで行い、結果を受け取ってから次の手順に進むこと」という指示文を明記する対応でした。

ところが、この修正をpushした同日中に同じ症状が再発してしまいました。
指示自体は誤っていないはずなのですが、確実性が足りなかったと考えられます。

理由として思い当たったのは以下の点です。

  • 巨大なシステムプロンプトの奥に埋もれた1行
    • スラッシュコマンドのMarkdownは、執筆規約やチェックリストなど他の詳細なルールと合わせて数万トークン規模のシステムプロンプトの一部として読み込まれる
  • 1行の指示への依存
    • サブエージェント呼び出しの判断が、その巨大なコンテキストのどこかにある1行の指示だけに依存していると、モデルが毎回確実にその指示を遵守するとは限らない

つまり「自然言語による指示の強化」は、無人実行かつ失敗が許容できないクリティカルパスに対する対策としては、再現性・確実性の面で不十分でした。

対応2: 構造そのものを変える

「フォアグラウンドで呼べ」という指示を強めるのではなく、そもそもサブエージェントへの委譲という構造自体をやめる方向で対応しました。

  • 修正前
    • メインのエージェントがレビュー用サブエージェントにAgent/Taskツールで委譲し、その回答を待って反映する
  • 修正後
    • サブエージェントの定義ファイル(レビュー観点・禁止事項が書かれたMarkdown)を、メインのエージェント自身がReadで直接読み込み、そこに書かれた観点を自分自身の推論として成果物に適用する。サブエージェントの呼び出しは行わない

この方式なら、「バックグラウンドかフォアグラウンドか」という概念そのものが存在しなくなるため、指示の見落としや解釈のブレによる再発が構造的に起こり得なくなります。

ただじ、対話的に使うスラッシュコマンドにはこの制約は課していません。
対話セッションでは後続ターンで完了通知を正しく受け取れるため、素直にサブエージェントへ委譲する設計のままで問題ないと判断しています。

注意として、サブエージェントを廃止するとレビューが甘くなる可能性があります。
ここはトレードオフとして覚えておいた方が良いです!

締め

今回の件から、無人実行のエージェント運用に対して汎用できる点は以下の通りです。

  • 無人実行は「1回のセッションで完結する」前提で設計する
    • 後続ターンでの非同期な通知受け取りに依存する機能(バックグラウンドタスク、完了通知など)は対話セッションを前提にしていることが多く、CI環境ではそのまま動かないことがある
  • 自然言語の指示強化だけで巨大なシステムプロンプト内の1分岐を制御しようとしない
    • 間違えると成果物が静かに生成されないようなクリティカルな分岐は、指示文の強化ではなく、その分岐が発生しないように構造そのものを設計し直す方が確実
  • 「ジョブが成功扱いで終わる」ことと「意図した成果物が作られた」ことは別物
    • 無人実行ワークフローのヘルスチェックは、Actionsの成功/失敗ステータスだけでなく、期待される成果物(PR・コメント・ファイルなど)が実際に作られたかを見るべき
  • 定期的な不具合は、モデルの「毎回同じ判断をする」という前提が崩れているサインであることが多い
    • 決定的な条件分岐に見えても、内部的には確率的な判断(今回はバックグラウンド/フォアグラウンドの選択)に依存している可能性を疑う

「失敗がエラーとして表面化しない」タイプの不具合は特に見つけにくいので、無人実行のパイプラインを組むときは成果物ベースのチェックを合わせて用意しておくのがお勧めですね!

結構長い間苦戦した事象でしたので、解決してよかったです。

ただサブエージェントを廃止するとレビューが甘くなるという話も聞くので、人によってはもう一工夫する必要があるかもしれません。

以上となります。
AIの使い方を考えさせられる件でした(^^
それではお疲れさまでした。