2026-09-04 · git · githubactions · 設計 · claudecode
並列開発を殺すのは、いつも共有ファイル
複数の作業を並列で走らせようとして、こうなったことはありませんか。
- 3つの機能を別ブランチで進めたら、全部が同じファイルでコンフリクトした
- 解消してマージしたら、別のブランチがまた同じ場所で衝突した
- 結局1本ずつ回すことにした
原因はたいてい共有ファイルです。 そして、これは分割の仕方で避けられます。
AI に並列で実装させる文脈で何度も踏んだので、パターンと対処を書きます。 人間のチームでも同じことが起きるので、AI に限った話ではありません。
犯人はだいたい決まっている
実際に衝突するファイルは、ほぼこの5種類でした。
- ルーティング定義 —
routes/index.ts、App.tsxの<Route>一覧 - 型の集約ファイル —
types/index.tsのような re-export の束 - DI コンテナ・エントリポイント — 依存を1箇所に登録する場所
package.json— 依存追加が重なる- マイグレーションのインデックス — 順序を持つ一覧
共通点があります。「一覧」であること。 新機能を足すたびに1行増える構造は、 複数人(複数エージェント)が同時に触ると必ず同じ場所を編集します。
さらに悪いのは、ここでのコンフリクトは意味的には無害なことです。 Aさんが1行足し、Bさんが1行足しただけ。両方残せば正しい。 なのに Git は自動で解決できず、人間が止まります。
対処1: 共有ファイルだけを先に片付ける
一番効きます。共有ファイルを触る作業を1つだけ切り出して、先に通します。
Issue #10 ルート定義に3つのエントリを追加 Scope: src/routes/index.ts
Issue #11 設定画面の実装 Scope: src/pages/settings/** Depends on #10
Issue #12 請求画面の実装 Scope: src/pages/billing/** Depends on #10
Issue #13 通知画面の実装 Scope: src/pages/notifications/** Depends on #10
#10 は「まだ存在しない画面へのルートを先に足す」だけなので、数分で終わります。
その後 #11 #12 #13 は共有ファイルを一切触らないので、完全に並列で走ります。
順序が逆だと成立しません。#11 を実装しながらルートも足す形にすると、
3つが同じファイルを取り合います。
「まだ無い画面へのルートを足す」のは変か
一時的にビルドが壊れるのでは、と思うかもしれません。実際には、 プレースホルダのコンポーネントを置いておけば通ります。
// src/pages/settings/index.tsx ← #11 が中身を実装する
export default function Settings() {
return null;
}
空の箱を先に置く。 これだけで並列化できます。
対処2: 一覧を持たない構造にする
そもそも「一覧ファイル」を無くせるなら、そのほうが根本的です。
- ルーティング → ディレクトリ規約からの自動収集(ファイルベースルーティング)
- 型の集約 →
types/index.tsを作らず、各モジュールから直接 import する - マイグレーション → ファイル名のタイムスタンプで順序を決め、インデックスを持たない
これ自体を1つの作業として切り出す価値があります。一度やれば、以後の並列化が ずっと楽になります。
ただし既存プロジェクトで急にやると影響範囲が広いので、 「衝突が3回起きたら構造を変える」くらいの基準を持っておくといいです。
対処3: 触れないものは、触れないと決めておく
AI に実装させる場合、これが効きます。担当範囲を明示し、 範囲外に出たら止めて報告させる。
- 担当している作業の Scope に書かれたパス以外を変更しない
- Scope 外の変更が必要になったら、実装を止めて報告する
黙って範囲を広げられると、分割が間違っていたという情報が消えます。 止まってくれれば、そこで分割を直せます。
実例です。ある作業で、実装者から報告が上がってきました。
tsc --noEmitがnode:pathの型を解決できません。devDependenciesに@types/nodeが無いためです。package.jsonは Scope 外なので変更していません。 別作業として切り出すか判断をお願いします。
これは土台の不足であって、その作業のせいではありません。
勝手に package.json を触られていたら、その PR に無関係な変更が混ざり、
レビューの基準がぼやけていました。別作業として切り出して、独立した PR で直しました。
対処4: マージは直列にする
分割が正しくても、マージまで並列にすると台無しです。
3つの PR を同時にマージすると、それぞれ単体ではテストが通るのに、 3つ入れた後で壊れることがあります。そしてどれが原因か切り分けられません。 結局3つとも調べ直すことになり、直列にマージするより遅くなります。
1件ずつマージすれば、main が進むたびに次の PR はその状態に対して判定されます。
壊れたら直前の1件が原因だと確定します。
実装は並列、マージは直列。 これが実務的な結論でした。
衝突は症状であって原因ではない
コンフリクトが繰り返し起きるとき、解消のやり方を改善しても意味がありません。 分割の仕方が間違っています。
コンフリクトの頻度は、分割の質を測る指標として使えます。
| 頻度 | 読み取れること |
|---|---|
| ほぼ起きない | 分割が効いている |
| たまに起きる | 想定外の依存がある。その回だけ解消すればよい |
| 毎回起きる | 共有ファイルを複数の作業が取り合っている。分割を見直す |
毎回起きているのに、その場その場で解消して回ると、症状は消えますが原因は残り、 コストは増え続けます。
まとめ
- 並列開発を止めるのは、たいてい「一覧」の構造を持つ共有ファイル
- 共有ファイルを触る作業を1つだけ先に通す。空の箱を先に置けばよい
- 一覧を持たない構造(規約ベースの自動収集)にできるなら、そのほうが根本的
- 担当範囲の外に出たら止めて報告させる。黙って広げられると分割ミスが見えなくなる
- 実装は並列、マージは直列
- コンフリクトの頻度は分割の質を測る指標。毎回起きるなら分割を疑う
この分割の考え方を、Claude Code のサブエージェント構成として明文化したものを MIT で公開しています。
Claude Code に設計・実装・レビューを別々の人格として分担させ、GitHub Issue と
ブランチを軸に並列開発を回すための設定一式を MIT で公開しています。
コピーして ./setup.sh を叩けば動きます。技術スタックには依存しません。
https://github.com/quintetkit/quartet
このワークフローだけで実際にツールを 1 つ作りました。Issue の分割から PR、 レビュー、マージまで記録が全部残っています。うまくいかなかった箇所も消していません。
https://github.com/quintetkit/mdlinkcheck
UI 設計人格・レビュー基準・Issue 単位の並列実行スクリプト・実践ガイド 10 章を 足したものは製品ページにあります。