並行実行するAIエージェントとTOCTOU — PRのheadを取り直す理由
状態を確認してから使うまでの間に状態が変わる、というTOCTOU(Time-of-check Time-of-use)はファイルシステムだけの問題ではない。同じリポジトリを複数のAIルーティンが並行して触る運用で、レビュー指摘への対応がこの型で衝突した実例と、機械的に確かめて回避した手順を整理する。
TOCTOUとは
TOCTOU(Time-of-check Time-of-use)とは、リソースの状態を確認してから実際に使うまでの間に状態が変わり、確認結果が無効になる不具合の型である。
CWE(共通脆弱性タイプ一覧)の定義はこうである。「プログラムはリソースを使用する前にその状態を確認するが、確認と使用の間にリソースの状態が変化しうる」。典型例はファイルの存在確認と書き込みの間にファイルが差し替えられるシンボリックリンク攻撃だが、この型は攻撃を想定しない場面でも起こる。確認と使用の間に別の主体が割り込める状況であれば十分である。
同じリポジトリを複数のAIルーティンが触る
本システムは、Cloudflare上で動くAIのルーティンがコードとレビューを回している。実装を進めるルーティンと、指摘を出すレビュアーが別々のスケジュールで独立に起動し、同じGitHubリポジトリの同じPRを対象に動くことがある。ここでいう「確認」はPRのhead(最新コミット)を読む操作であり、「使用」はそのheadを基点に修正を作って積む操作である。この2つの間に、別の実行が同じPRに触れる余地がある。
実際に起きた2つの衝突
同じ指摘を二重に直す衝突。 あるPRのBLOCKER指摘に対応するため、読んだ時点のheadを基点に修正を作っていたところ、作業の途中で別の実行が同じ指摘を先に直してpushしていた。この衝突は、pushする直前にheadを取り直したことで気付けた。読んだ瞬間のheadと、pushする瞬間のheadが違えば、その間に何かが起きている。
マージに追い越される衝突。 別の日には、レビューでCHANGES_REQUESTEDが付いたPRに対して修正をコミットしている最中、その間に別の実行が指摘前の古いコミットのままPRをマージした。修正は完成していたが、マージ済みブランチに積んでも本流には二度と入らない。修正が完了しているかどうかと、本流が直っているかどうかは別の事実であり、後者は「PRがまだopenか」を機械的に確認しないと分からない。
この2つは症状が違うが、原因は同じ形をしている。確認してから使うまでの間隔の長さが、衝突が起きる確率を決める。 差分を作るのに時間がかかるほど、その間に他の実行が状態を変える余地が増える。
手順 — 確認と使用の間隔を機械で埋める
| # | 手順 | 埋める間隔 |
|---|---|---|
| 1 | 着手前にheadを読む | — |
| 2 | 修正を作る | ここで時間が空く |
| 3 | pushの直前にheadを再取得する | 作業中に生じた変化を検出する |
| 4 | 変化していたら、相手の修正を読む | 同じ穴を別の形で塞いだ可能性がある |
| 5 | 相手の修正が同じ穴を塞ぐか、実際に確かめる | 「直したと書いてある」は確認にならない |
| 6 | 塞げていれば自分の作業は捨てる。force pushで上書きしない | 相手の検査ごと消さない |
| 7 | pushの直前にPRがopenかを確認する | マージ済みブランチに積まない |
ここで重要なのは4と5である。headが動いていたからといって、相手の修正内容を読まずに自分の版で上書きするのは、確認を1回増やしただけで解決になっていない。指摘された入力を相手の実装に実際に通し、落ちないことを確かめて初めて「塞がっている」と言える。 塞ぎ方が違うだけであれば、相手の設計を基点に採り、足りない差分だけを積む。
この設計から引ける原則
TOCTOUの一般的な対策は「確認と使用を1つの不可分な操作にする」ことだが、複数のAIエージェントが同じ状態を編集する場面では、確認と使用を1操作に融合するのは難しい。現実的な対策は、間隔を短く保つこと(使用の直前に確認し直す)と、衝突を検出したときに機械的に降りる規則を持つことである。 「自分の作業のほうが良いはず」という主観で上書きを選ばず、根拠はコメントで示す。
FAQ
Q. ロックを取得すれば防げませんか。 GitHubのPRという共有リソースに対して、独立に動く複数のAIルーティンが排他ロックを取り合う仕組みは持っていません。ロックの取得と解放自体が新しいTOCTOUを生む可能性もあり、現実的な対策としては衝突検出と降参規則を選んでいます。
Q. 衝突はどのくらいの頻度で起きますか。 実行スケジュールが重なる時間帯ほど起きやすくなります。本システムでは指摘対応と実装が同じ1時間の枠内で複数回起動することがあり、その枠内で複数件の衝突が確認されています。
Q. 衝突を検出せずに上書きするとどうなりますか。 相手が追加した検査や修正ごと消える可能性があります。マージされた履歴を書き換える強制pushは特に危険で、通常は避けるべき操作です。
Q. 人間のレビュープロセスにも同じ話は当てはまりますか。 当てはまります。複数人が同じブランチに同時に手を入れる状況は同じ形の衝突を起こします。違いは、AIルーティンは人間より高頻度・高並行度で動くため、間隔を機械で埋める規則がないと衝突が実際に顕在化する点です。
複数のAIエージェントが同じコードベースを並行して触る運用を検討している場合、こうした衝突を前提にした設計についてご相談を承っています。[お問い合わせはこちら](/contact)。
出典
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition(参照 2026-08-14)