dev-agent-teamシリーズの5本目。
前回(Claudeが便利機能を出したら、自作ワークフローは不要になるのか)で、
Phase 2を並列化する Dynamic Workflow /dev-agent-discovery を実装した。
ただし構文検証だけで実走はしていなかった。今回はそれを実際に動かして出たバグの話。
- 3ファイル指定のはずが、scope暴走で約21エージェントに膨張した
- 原因は args がJSON文字列で渡る経路で paths を取りこぼしていたこと
- 正規化+上限+診断ログで修正し、4エージェントに収束。構文検証と実走は別物
前回の記事の最後に「実走は未実施、実挙動はClaude Code上で起動して確認する必要がある」と書いた。
その確認を PR #17 でやったところ、
さっそくバグが出た。構文チェック(node --check)では絶対に出ない種類のバグだ。
1. 症状:3ファイルのはずが約21エージェント
動作確認のため、対象を3ファイルに絞って起動した。 ところが実際には、scopeエージェントがリポジトリ全体(約20ファイル)を列挙し、 そこから約21個のsubagentに膨張した。
当然ながら長時間化し、見た目には 「動いていない / エラーでコケた」ようにしか見えない。 実際は暴走的に正しく(?)走り続けていただけなのだが、 3ファイル渡したのに全ファイルを舐めている時点で明確な不具合だ。
2. 原因:args が JSON 文字列で渡る経路
根っこは args(ワークフローへの引数)の渡り方にあった。
起動経路によっては、args がJSON文字列として渡ってくる。
ところが元コードは typeof args === 'string' のとき、
その文字列を丸ごと focus(調査の焦点)として扱い、paths を捨てていた。
つまり「3ファイルのパス」を含んだJSON文字列が来ても、パスは展開されずに焦点文字列扱いになる。
結果 seedPaths(起点ファイル)が空になり——
空だからリポジトリ全体を列挙するscopeフォールバックが発火した。
指定したつもりのパスが、型の取りこぼしで無かったことにされていたわけだ。
「3ファイル渡したのに無視される」のではなく、「3ファイルが空として解釈され、空ならば全部見る挙動が正しく発火していた」。
3. 修正
対処は引数の正規化と、暴走そのものへの安全弁の2段構えにした。
-
normalizeArgs()を追加 —argsが文字列(プレーン/JSON)/配列/オブジェクト/未指定の どの形で来ても正規化する。JSON文字列はパースしてpathsを復元する。 渡り方のゆらぎを入口の1か所で吸収する。 -
maxFiles上限を導入 — 明示paths・scope列挙のどちらの経路でも上限でsliceする。 仮に再び起点が空になっても、青天井で膨張しないための安全弁。 -
WF_DIAG診断ログを追加 — 「いま何ファイルを起点に、何エージェント起動しようとしているか」が見えるようにした。 今回のように「動いてない」のか「暴走してる」のかを切り分けられるようにするため。
ついでに、uninstall.sh 冒頭コメントの古い記述も直した。
これはワークフロー自身の openQuestions(未解決の確認事項)が検出した不整合で、
「adopt-project.md のみ削除」という説明が実装(COMMANDS[] / WORKFLOWS[] を削除)と
食い違っていた。調査ワークフローが自分の周辺の矛盾を見つけてくれた格好だ。
4. 検証:4エージェントに収まった
修正後、paths 3件 + maxFiles 3 のbounded runで動かすと:
- agent数 = 4(reader 3 + synthesize 1)。scope暴走なし
- investigation-reportの全5セクション(relatedFiles / similarImplementations / conventions / tentativeChangeList / openQuestions)が構造化されて返却
- 内部に人間ゲートなし(確認事項は openQuestions に列挙=前回設計したとおり)
約21 → 4。指定どおりに収まった。
5. まとめ:構文検証と実走は別物
このバグから得た教訓はシンプルだ。
node --checkが通ることと、意図どおり動くことは全く別。今回のバグは構文的には完全に正しい- 外部から来る入力(args)は、渡り方のゆらぎを入口で正規化する。文字列/JSON/配列/オブジェクトを1か所で吸収
- 並列ファンアウトには必ず上限(maxFiles)を置く。起点が壊れたときに青天井で膨張しない安全弁
- 「動いてない」のか「暴走してる」のかを切り分ける診断ログを最初から入れる
前回の記事で「型をエンジンに乗せる」話を書いたが、 乗せた直後の初回実走でさっそく足をすくわれた。 仕組みは作っただけでは終わらず、実際に回して初めて盲点が見える—— これはこのシリーズで繰り返し出てくるテーマで、今回もまさにそれだった。 構文が通ったから安心、ではなく、一度走らせて目で確かめるまでが実装だ。
リポジトリはこちら: fecot/dev-agent-team