サブエージェント並列運用の失敗集
worktreeには、gitignoreした検証スクリプトが無い。並列の3レーンが全部これを踏んだ。プロンプトに入れるガードと、回収の確かめ方。
PR:この記事には、当サイトが参加する(予定の)アフィリエイトプログラムの紹介枠(学習サービス)があります。内容と評価は、報酬の有無と関係なく決めています。
サブエージェントを並列で回すと、1体で動かしていたときには出なかった事故が起きます。この記事では、worktree まわりの罠を手元で再現し、プロンプトに入れているガードと、成果物の回収の確かめ方をまとめます。再現した環境は、編集部の検証環境(Linux、git 2.47.3、Claude Code 2.1.301、2026-10-16)です。
製品ごとの並列の上限は「AIコーディング3製品を運用で比較」にまとめています。
worktree に検証スクリプトが無い
Claude Code のサブエージェントは、isolation: worktree を指定すると、一時的な git worktree の中で動きます。この worktree は、既定では親セッションの HEAD ではなく、既定のブランチから切られます(出典:https://code.claude.com/docs/en/sub-agents 、2026-10-16確認)。ここに落とし穴があります。
編集部では、完了の判定に使う verify.sh を .claude/ に置き、.claude/ を gitignore していました。worktree はコミット済みのファイルしか持たないので、verify.sh がありません。手元で再現しました。
$ git init -q -b main repo && cd repo
$ echo ".claude/" > .gitignore && mkdir .claude && touch .claude/verify.sh
$ git add . && git commit -qm init
$ git worktree add -q ../wt -b lane1
$ ls -a ../wt
. .. .git .gitignore
$ ls ../wt/.claude
ls: cannot access '../wt/.claude': No such file or directory
並列のレーンを出したとき、3レーンすべてがこれを踏みました。worktree の中から本体の verify.sh を呼ぶと、スクリプトの書き方によっては、本体のチェックアウトを検証してしまいます。いまは、レーンを作るたびに検証スクリプトをコピーするか、検証対象のパスを引数で明示しています。
引き継ぎメモは worktree ごとに分かれる
本体の引き継ぎメモには「フェーズ0、未着手」と書いてありました。ところが worktree のほうは、39コミット先に進んでいました。セッションを始めるときは、まず git worktree list を見ます。
長く使った worktree で、タスク表を本体と worktree の両方で更新し、squash merge したこともあります。396行あったタスク表が、worktree 側の266行の古い版で上書きされました。いまは、メモやタスク表のようなメタファイルは、本体の側でだけ更新しています。
引き継ぎメモそのものの設計は「エージェントのメモリ設計」に書きました。
プロンプトに入れているガード
サブエージェントへの指示には、次の項目を毎回入れています。
git commit --amendは禁止。- 本体と worktree のパスを両方書き、「本体には触らない」と明記する。これを書かなかったときに、実際に本体側を汚されました。
- 発見したことは、報告だけでなくファイルにも書かせる。報告が失われても残るようにするためです。
競合に敏感なリファクタでは、さらに2つを足します。触ってはいけない関数を file:line で並べた「凍結ゾーン」と、許可する機械的な置換1行です。そのうえで「迷ったら BLOCKED で止まれ」と書きます。この形にしてからは、指示した範囲の外を触ることがなくなりました。
テストを足させても、壊して落ちるかを確かめるまでは信用しません。「緑のテストは何も証明しない」に書きました。
テストは直列、ビルドは並列
夜間に、エージェント7体で7本の PR を約10時間かけて進めたことがあります。ビルドは並列で問題ありませんでした。テストは、共有の資源を取り合うので、mkdir のロックで1本ずつ流しました。mkdir は、ディレクトリが既にあれば失敗するので、そのまま排他に使えます。
until mkdir .test.lock 2>/dev/null; do sleep 0.2; done
run-tests; rmdir .test.lock
3レーンで同時に試すと、開始と終了がきれいに順番に並びました。
lane1 start 10:12:52.30
lane1 end 10:12:53.30
lane2 start 10:12:53.31
lane2 end 10:12:54.31
lane3 start 10:12:54.32
lane3 end 10:12:55.33
期待するテスト数は、「本体で実行されたテスト数+各レーンが追加したテスト数の合計」で出し、足し算が合うかを照合します。
回収は機械で確かめる
- テスト名の集合を比べる:リファクタの前と後で、テスト関数名の集合の差分を取ります。消えた名前があれば、それが答えです。
- マージの diffstat を読む:
git merge --ff-onlyの出力の diffstat は必ず読みます。Bin 0 -> N bytesのような、意図しないバイナリはここで初めて見えます。 - 「できない」と報告されたら裏を取る:サブエージェントが「指示どおりには実装できない」と言ってきたら、指示する側が自分で確かめます。代わりの案を勝手に実装させません。
自己申告を git の実数と照合する仕組みは「AIの「完了しました」を信じない仕組み」にあります。
古いモジュールを丸ごと消すリファクタを、どの順番で分けて任せるかは「数千行をリファクタリングで消す」に書きました。
見積もりは楽観的に出る
計画の段階で出した作業量の見積もりは、毎回20〜25%ほど楽観的でした。並列にする前提で予定を組むなら、その分を最初から足しておきます。
まとめ
- worktree には、gitignore したファイルが無い。検証スクリプトはレーンごとに用意する。
- メタファイルは本体の側でだけ更新し、セッションの始めに
git worktree listを見る。 - テストは追加だけ、amend は禁止、本体のパスは明記、迷ったら BLOCKED。
- 回収は、テスト名の集合と diffstat で機械的に確かめる。