dev-agent-teamシリーズの5本目。 前回(Claudeが便利機能を出したら、自作ワークフローは不要になるのか)で、 Phase 2を並列化する Dynamic Workflow /dev-agent-discovery を実装した。 ただし構文検証だけで実走はしていなかった。今回はそれを実際に動かして出たバグの話。
この記事の要点
  • 3ファイル指定のはずが、scope暴走で約21エージェントに膨張した
  • 原因は args がJSON文字列で渡る経路で paths を取りこぼしていたこと
  • 正規化+上限+診断ログで修正し、4エージェントに収束。構文検証と実走は別物
症状約21 agentに膨張
原因argsのpaths取りこぼし
修正正規化 + 上限 + ログ
検証4 agentに収束

前回の記事の最後に「実走は未実施、実挙動はClaude Code上で起動して確認する必要がある」と書いた。 その確認を PR #17 でやったところ、 さっそくバグが出た。構文チェック(node --check)では絶対に出ない種類のバグだ。

1. 症状:3ファイルのはずが約21エージェント

動作確認のため、対象を3ファイルに絞って起動した。 ところが実際には、scopeエージェントがリポジトリ全体(約20ファイル)を列挙し、 そこから約21個のsubagentに膨張した。

当然ながら長時間化し、見た目には 「動いていない / エラーでコケた」ようにしか見えない。 実際は暴走的に正しく(?)走り続けていただけなのだが、 3ファイル渡したのに全ファイルを舐めている時点で明確な不具合だ。

2. 原因:args が JSON 文字列で渡る経路

根っこは args(ワークフローへの引数)の渡り方にあった。 起動経路によっては、argsJSON文字列として渡ってくる。 ところが元コードは typeof args === 'string' のとき、 その文字列を丸ごと focus(調査の焦点)として扱い、paths を捨てていた

つまり「3ファイルのパス」を含んだJSON文字列が来ても、パスは展開されずに焦点文字列扱いになる。 結果 seedPaths(起点ファイル)が空になり—— 空だからリポジトリ全体を列挙するscopeフォールバックが発火した。 指定したつもりのパスが、型の取りこぼしで無かったことにされていたわけだ。

「3ファイル渡したのに無視される」のではなく、「3ファイルが空として解釈され、空ならば全部見る挙動が正しく発火していた」。

3. 修正

対処は引数の正規化と、暴走そのものへの安全弁の2段構えにした。

ついでに、uninstall.sh 冒頭コメントの古い記述も直した。 これはワークフロー自身の openQuestions(未解決の確認事項)が検出した不整合で、 「adopt-project.md のみ削除」という説明が実装(COMMANDS[] / WORKFLOWS[] を削除)と 食い違っていた。調査ワークフローが自分の周辺の矛盾を見つけてくれた格好だ。

4. 検証:4エージェントに収まった

修正後、paths 3件 + maxFiles 3 のbounded runで動かすと:

約21 → 4。指定どおりに収まった。

5. まとめ:構文検証と実走は別物

このバグから得た教訓はシンプルだ。

前回の記事で「型をエンジンに乗せる」話を書いたが、 乗せた直後の初回実走でさっそく足をすくわれた。 仕組みは作っただけでは終わらず、実際に回して初めて盲点が見える—— これはこのシリーズで繰り返し出てくるテーマで、今回もまさにそれだった。 構文が通ったから安心、ではなく、一度走らせて目で確かめるまでが実装だ。

リポジトリはこちら: fecot/dev-agent-team