サブエージェント(Agent ツール)に作業を任せる形も、複数の Claude Code をヘッドレスで起こして仕事を分担させる技術検証では、候補の1つでした。PreToolUse フックの defer で人の承認を待つ関門を作ると、トップレベルのエージェントでは「止まって、承認後に --resume で続ける」往復が成立していました。ところが、サブエージェントの中で承認の要る操作をさせると、関門は止めるところまでは働きましたが、往復は成立しませんでした。
結論
- サブエージェントの中でもフックは発火し、ツールは止まりました(ファイルの書き換えは起きなかった)。
- しかしプロセスは
tool_deferredで止まらず、end_turnで終わりました。親のエージェントは「失敗した」と解釈して先へ進みました。 - 承認して
--resumeしても、保留したツールは実行されませんでした。 - そこで、承認を求めることのあるエージェントはサブエージェントにせず、独立のプロセスとして起こすことにしました。あわせて、すべてのエージェントから
Agent・Taskを--disallowedToolsで外しました。
試した環境
- Mac、Claude Code 2.1.170
claude -p --output-format stream-json --verbose --include-hook-events --settings <フックの設定> --setting-sources ''で起こしました。フックは、状態ファイルの中身を見てdefer・allow・denyのどれかを返すだけの台本です。
試した仕組みは、エージェントが節目ごとに承認の申請を出して止まり、人が承認すると続きから走る作りでした。
トップレベルでは成立していた
前提として、トップレベルのエージェントでは、次のとおり往復が成立していました。
- フックが
deferを返すと、プロセスは exit 0 で終わり、result のstop_reasonがtool_deferredになる。保留したツールはdeferred_tool_useに入る。 - 承認して
--resumeすると、フックがもう一度呼ばれ、allowを返すと保留したツールが実行される。
起こす側は、stop_reason が tool_deferred かどうかで「承認待ち」を見分けます。is_error は false のままなので、こちらでは見分けられません。
サブエージェントの中で試した
同じフックを掛けたまま、エージェントに Agent ツールでサブエージェントを起こさせ、その中でフックに掛かる操作をさせました。起きたことを図にすると、次のようになります。
フックは確かに発火し、ツールも止まりました。サブエージェントがしようとしたファイルの書き換えは起きていません。ここまでは、関門が効いているように見えます。
ところが、プロセスは tool_deferred ではなく end_turn で終わりました。親のエージェントは、サブエージェントの操作が通らなかったことを「失敗した」と受け取り、そのまま次の作業へ進んでいました。そのあと承認して --resume しても、保留していたはずのツールは実行されませんでした。
何が困るか
関門が「止める」ところまでは働くので、一見すると守られているように見えます。ところが、起こす側が stop_reason で承認待ちを見分けていると、この回は end_turn で終わっているので、承認待ちには見えません。承認したとしても、その操作は実行されません。親のエージェントは、承認の要る操作を飛ばしたまま、先の作業を進めてしまいます。

どうしたか
承認を求めることのあるエージェントは、サブエージェントにせず、起こす側から独立のプロセスとして起こすことにしました。そのうえで、すべてのエージェントから Agent と Task を --disallowedTools で外しました。
claude -p "<指示>" --disallowedTools Agent,Task …
--allowedTools に Agent を入れないだけでは足りませんでした。同じ版で測ったところ、エージェントは --allowedTools に無い道具も使い、その中には Agent も入っていました。名前を挙げて外す --disallowedTools は効き、サブエージェントにも引き継がれました。
どう確かめるか
フックに掛かる操作をサブエージェントにさせて、stream-json の最後の result の stop_reason を見ます。tool_deferred ではなく end_turn になっていれば、この記事と同じことが起きています。あわせて、エージェントが使った道具の名前を stream-json から拾い、Agent・Task が呼ばれていないことを確かめます。
確かめていないこと
- 2.1.170 より後の版でも同じかどうか。
おわりに
サブエージェントの中の defer は、操作を止めることはできても、承認を待ってから続けることはできませんでした。承認を挟む仕事は、独立に起こしたプロセスに持たせるのが確実でした。
※ この記事に書いた Claude Code の振る舞いは、執筆時点で私たちが測ったものです。いまは変わっている可能性があります。


