Vondet

記録の一覧

実践

Claude Code を並列実行して、開発を速くする —— git が止めてくれるものと、止めてくれないもの

2本目の窓は、いま一番安い高速化です。ファイルの衝突は git が止めます。判断の衝突は、誰も止めません。

Vondet / 2026-09-06 / 読了 9 分

先月、1つの問いに答えるのに、エージェントのセッションが少なくとも12本かかりました。CLAUDE.md に1行足したら、Claude Code の動きは変わるのか。

結果として数えられたのは、そのうち9本です —— 対照群、処置群、そして間違いに気づいてやり直した処置群。残りは、この記事で書く衝突で消えました。

これを1本ずつ順番に回さず、何本かずつ同時に走らせて、1日で終わりました。

これが Claude Code を並列実行する、ふつうの場面です。並列開発でいま一番安い高速化でもあります。大きいモデルも、うまいプロンプトも要りません。窓をもう1つ開けて、1本目が待っているあいだにやることを渡すだけです。

理由は待ちです。テストを流している、依存を入れている、40個のファイルを読んでいる —— そのあいだエージェントはこちらに何も聞いてきません。その時間はまるごと、こちらの手が空いています。2本目のセッションは、その時間を「もう出来上がっているブランチを読む時間」に変えます。

以下は、うまくいっている4つと、4つを全部やっても残る1つの問題です。4つはどれも私たちの発明ではありません。git worktree も、役割分担も、窓の分割も、すでに多くの人がやっていて、実際に効いています。面白いのは、その後に残るほうです。

うまくいっている4つ

1. git worktree で作業ツリーを分ける

同じチェックアウトを複数エージェントが編集する、が分かりやすい失敗で、git worktree が分かりやすい対処です。1行で、自分のブランチを持った2つ目のディレクトリができ、.git は1つを共有します。

git worktree add ../myproject-review -b review-auth

これで2本目のセッションは自分のファイルを持ちます。複数セッションを同時に走らせても、片方の書き込みが、もう片方の編集の途中に現れることはありません。

初回だけ、確かめてほしいことが1つあります。git worktree add <パス> はコミットを指定しなければ、いまの HEAD から分岐します。ですが、隔離されたツリーを用意してくれる道具を使っている場合、その既定は同じとは限りません。

私たちの場合、いま立っているコミットではなく、リポジトリの既定ブランチから分岐していました。ブランチの上に用意したものは、そこには単に無い。そしてセッションは何事もなく走ります。

古いツリーはどこも壊れていないからです。妥当なツリーです。

これで、逆の結論を公開しかけました。上の実験で、試したかった変更はブランチの上にあり、隔離されたツリーは既定ブランチから来ていて、「処置群」の2回は、処置なしで走っていました。気づいたのは、結果を書き上げたあとに、分岐元を確かめたときです。

なので、作ったら見る。

git -C ../myproject-review log --oneline -1

2秒です。そしてこの2秒が、「結果」と「結果の正反対」の差でした。

そして、worktree が分けてくれないものを知っておく。分かれるのはファイルです。データベースも、開発サーバのポートも、.env も、キャッシュも、レート制限も、2本が両方話しかける外部のサービスも、分かれません。

私たちの場合、あるセッションが自分で、「外部の共有サービスへの書き込みは worktree では隔離されない」ことを正しく見抜きました。git は隔離されていて、共有された状態は隔離されていない。

始める前に、2本が両方触るもののうち、ファイルでないものを数えてください。

2. 役割を分ける

うまくいく組み合わせは、実装2本ではありません。非対称です。

  • 片方が実装し、片方がその差分をレビューする
  • 片方が実装し、片方がコードベースを読んで質問に答える
  • 片方が実装し、片方がテストを書く

調査とレビューは読むだけなので、そもそも worktree が要りません。ファイルの上では何とも衝突しません(ただしレート制限も外部サービスも共有のままです。読むだけで買えるのはファイルの安全であって、隔離ではありません)。いちばん安い2本目なので、並列エージェントを試すならここから始めるのを勧めます。

3. 触る範囲を先に決める —— しかも「状況次第」にしない

2本目を起動する前に、どちらがどのパスを触ってよいかを書き出します。重なったら、片方を読むだけの仕事に変える。

分かるまでに時間がかかったのは、こちらです ——「状況次第」ではなく「常に」で書く。私たちには「あるリポジトリから、別のリポジトリには書かない」という線があり、「並行で何か走っているときは書かない」ではなく「書かない」と書いてあります。理由は、並行で何か走っているかを、こちら側から知る手段が無いからです。

相手の状態を知っていないと適用できない境界は、境界ではありません。願いです。

もう片方の窓が見えないなら、「大事なときは気をつける」という選択肢は、そもそも手元にありません。

4. 窓を分ける

tmux でも、端末のタブでも、エディタの分割でも、いま使っているもので構いません。道具の話ではありません。見えていない並列セッションは、走っていることを忘れるセッションで、忘れたほうが、まずいタイミングでコミットします。両方を見えるようにしておくか、両方を見る習慣にしておく。

4つを全部やっても残るのは、行ではなく結論の衝突です

これです。

ファイルの衝突は git が止めます。判断の衝突は、誰も止めません。

ここでの主張は狭いものです —— 上の4つはファイルを守るもので、私たちの午後を繰り返し奪っているのはファイルではない。

worktree も、ブランチも、コンフリクトも、ロックも、バイトを守る仕掛けです。そしてとても優秀です。同じ行を2本が編集したら、必ず分かります。

ですが、2本のセッションがぶつかるのは、行ではありません。結論です。

片方は「リトライはクライアント側だ」と決め、もう片方は「サーバ側だ」と決める。片方は設定キーを timeout_ms にし、もう片方は timeoutMs にする。

どちらのブランチも、それ自体としては筋が通っています。どちらもきれいにマージできます。何も赤くなりません。

気づくのはレビューか、もっと後です。

私たちに起きた2件を出します。どちらも、自分たちのリポジトリでエージェントを並列に走らせているときのものです。

1本が、もう1本のせいで、自分の仕事を消しました。同じ作業ツリーで3本を同時に走らせていました(これは分ける前の話で、分けた理由そのものです)。

そのうちの1本が、別の1本が作ったファイルに気づき、「この仕事はもう終わっている」と判断して、自分の下書きを消しました。

壊れたファイルはありません。git に報告することは何もありません。

そのセッションは、真実である事実を使って、妥当なことをしました。ただ、その事実はそのセッションの担当ではありませんでした。

1本がコミットして、もう1本の前提を壊しました。別の回で、先に片方の成果をコミットしました。

もう片方の仕事は、まさにその作業をすることでした —— コミットした時点で、やるべき仕事が存在しなくなっていたわけです。3回分が無効になりました。

ここでも、コンフリクトも、エラーも、壊れたファイルもありません。壊れたのは、もう片方が立っていた前提です。

2件は同じ形をしていて、しかもこの形は、1本を2本にした瞬間に現れます。

どちらの側も、自分に見えているものを使って動く。そして見えているのは、相手の成果物であって、相手の判断ではない。

決定は、いま走っているもう1本に届く場所に書く

対処は複雑ではありませんが、当てる場所を間違えると効きません。「これはどこに書き残すべきか」より狭い問いを立てます。「ここに書いたら、いま走っているもう1本に届くか」。

書く場所いま走っているもう1本に届くか
いまの会話届きません。ここがいちばん見落とされます。伝えた感じがして、届くのは1本だけです
リポジトリの中のファイルもう片方が読みに行ったときだけ。しかも読むのはそのツリーにある版で、こちらの版とは限りません
コミットメッセージコミットすれば届きます。頼まなくても届いたことがあります(下に実測)
instruction file(CLAUDE.md)次に起動するセッションには。すでに走っているセッションには、届かない前提で組む
共有された外部の状態(DB・サービス)即座に届きます。そしてそれは、worktree が守ってくれない理由と同じことです

このうち2行を広げます。

コミットメッセージは、意図より遠くまで届きます。分かったのは事故でです。上の実験で、何の実験かをコミットメッセージに書きました。

何も知らない被験者のはずだったセッションが git log を読み、自分が実験の中にいることを見抜いて、こちらに申告してきました。その回は台無しです。

ですが同時に、すでに走っているセッションに、こちらが指さしてもいないのに何かが届いたのを見た、唯一の場面でもあります。

「X と決めた」から「もう片方の窓が X を知っている」までの最短経路を探しているなら、それは X を実現した変更のコミットメッセージです。何をしたかだけでなく、なぜかをそこに書いてください。

(貼らずに始めた新しいセッションが、最初から何を知っているのか —— 時間をまたぐほうの半分は別の記事に書きました。この記事の末尾からたどれます。ここで扱うのは、いま開いているもう1本の窓だけです。)

走っている最中にルールを直しても、届かない可能性があります。隣の事例は測ってあります —— 自分たちのプラグインの指示文は、セッションが読み込んだ時点で固定されていました。原本を直し、ディスク上には正しく新しい版が入り、それでも走っているセッションは古い文面を使い続けました。同じセッションで開き直すと「instructions unchanged」と返ってきます。CLAUDE.md そのもので同じ試験はしていませんので、これは隣の結果であって、証拠ではありません。ただ、並列で走らせるときの安全側の前提はこうです ——「ルールを直しても、すでに走っているセッションには届かない」。ルールを変えたら、もう片方の窓を開き直す。

(届いたルールが守られるかどうかは、また別の失敗です。そちらも測ってあって、同じく末尾からたどれます。原因は記憶ではありませんでした。)

表より効く習慣が2つあります。

共有の場所には、書く前に読む。2本が両方書く場所には、置き換える前に、いま何が入っているかを見る手段が要ります。私たち自身、書けるのに読み返せない道具を出したことがあり、しかも書き込みは全置換でした。つまり2本で走らせると、2本目が1本目の記入を黙って消し、気づく手段すらありません。読めない共有面は、共有面ではありません。競合です。

判断の理由は、もう片方がこれから触るものの隣に書く。自分しか見ない場所に残した理由は、経路ではありません。

自分の2本で試すなら

2本目を起動する前に、具体的にはこれだけです。

  1. 1. どちらがどのパスを触ってよいかを書き出す。重なったら、片方を読むだけの仕事(レビュー・調査・テスト)にする。頭の中ではなく、1行でよいので書く。
  2. 2. ファイルでない共有物を数える —— DB、ポート、.env、外部サービス、キャッシュ。worktree はどれも分けません。
  3. 3. 隔離されたツリーを使うなら、分岐元をその場で確かめる。git -C <ツリー> log --oneline -1 を、意図したコミットと見比べる。新しい隔離の仕組みを使う初回に、1回だけでよい。
  4. 4. 決定は、理由つきでコミットメッセージに書く。「リトライを直した」ではなく「リトライはサーバ側へ。クライアントには書き込みが通ったか知る手段が無いから」。この一文が、もう片方の窓に要るものです。
  5. 5. 片方を取り込んだら、もう片方にそう伝える。相手が立っていた前提が変わっています。走っているエージェントは気づきません。もう存在しない世界の上に積み続けます。

1と5が、私たちの2件の両方を防げたものです。そしてどちらも道具ではありません。

この記事が示していないこと

速くなった量を測っていません

倍率もベンチマークも計測もありません。2.4倍でした、とは書きません。その実験をしていないからで、数字を作る気は無いからです。並列にする根拠はここでは仕組みの話(待ち時間が作業時間になる)で、冒頭の「12本以上を1日で」も、やったことの説明であって、短縮量の測定ではありません。

私たちの事故は2件です

判断の衝突が1時間あたり何回起きるか、セッションあたり何回かは数えていません。私たちは実験の回をいくつか失いましたが、あなたのところでは稀かもしれません。

1つのリポジトリ、1系統のモデルです

上の全部は自分たちのリポジトリで起きたことです。チームでも、モノレポでも、別ベンダーのエージェント同士でも試していません。

CLAUDE.md の読み直しは試していません

「指示文が固定される」の実測はプラグインのもので、instruction file のものではありません。隣の事例であって、その事例ではありません。

これが崩れる条件

  • 2本に本当に交わらない仕事を渡せるなら(別のサービス、共有の設定なし、共有の判断なし)、この記事の話は起きません。その場合は、何も考えずに2本走らせてください。
  • 決定をコミットメッセージに書いても何も変わらない(もう片方が結局読まない)なら、問題は経路ではなく、この記事は的を外しています。午後1回で試せることなので、そうなったら教えてください。

もう一段うまくやるなら

上の表を、時計を持ってもう一度見てください。

14時03分の決定から、15時20分のコミットまでを帯で塗った時間の図。帯の中にある点は共有された外部の状態だけ。コミットメッセージは15時20分に届き、リポジトリのファイルと CLAUDE.md はもっと後の、こちらが決められない時刻に届く。いまの会話は軸の上に現れない。
図:「どこに書くか」の5行を、時刻の軸に置き直したもの。帯は、14時03分に決めてから、それを運ぶコミットができる15時20分までのあいだ —— もう片方が、間違った前提の上に積める時間です。塗りつぶしの点は届く時刻が決まっているもの、中空の点は届く時刻をこちらで決められないもの、×は軸に乗らないもの(いまの会話は、もう1本には届きません)。帯の中に入る点は1つだけで、それは共有された外部の状態 —— 誰も経路として作っていない場所です。

こちらが意図して書きに行く行は、どれも、届くのは事が済んでからです。書いて、コミットして、そのあとどこかの時点で相手がたまたま見る。(即座に届く行が1つだけありますが、それは共有したくて共有した状態ではありません。)

ですが判断は、それを運ぶ変更より先に存在します。14時03分に「リトライはサーバ側へ」と決め、その一文を運ぶコミットができるのは15時20分です。

もう片方が間違った前提の上に積み続けられる時間は、まるごとその手前にあります —— そしてそこは、2本が両方走っている時間そのものです。

足りないのは、決めたその瞬間に決定を置ける場所で、しかも隣で走っている窓が、どちらもまだコミットしていないうちに見られる場所です。私たちはそれを作っていて、Vondet(vondet.com)といいます。エージェントが MCP 経由で読み書きする盤面で、上の2件があったから存在しています(逆ではありません)。まだ登録は開いていません。順番待ちがあるだけで、いまはそれで足りると考えています。

範囲を決めてコミットメッセージに理由を書く版を、自分の2本で試したら、結果を教えてください。特に、それでも判断がぶつかった場合に。