第14章:二つの手が、同じ場所を書き換えていた
2026年7月9日未明、75933a0というコミットが記録されました。20分後に3a0907cという別のコミットが、さらに10分後に3つ目のコミット40571adが積まれました。3つとも同じリポジトリ、同じ作業ツリーへの記録です。ところが、最初の2つを書いた手は、互いの存在にまったく気づいていませんでした。
これまでの章では、Windows APIやIMEという、awaseの外側にある相手が信頼できるかどうかを問うてきました。本章で問うのは、awaseを書いている手の側です。同じファイルを、同じ判断を、複数の手が別々の時に書き換えるとき、何を区切りとして立てればよいのか。答えは一つではなく、事件によって形を変えます。
git addが、知らない変更を拾いかけた
75933a0はBUG-21(Chromeのcold-start復帰処理の過剰発火)の修正、3a0907cはBUG-22(MS Edgeでのモード固着)の修正でした。症状も原因も別で直すファイルも別々でしたが、両者ともdocs/known-bugs.mdという1つのファイルに、自分のバグの節を末尾追記する形で書き込んでいたため、内容としては競合しませんでした。
問題は、この2つの修正を追っていたのが、別々のClaude Codeセッションだったことです。どちらも同じ作業ツリー上で、互いに気づかないまま並行して動いていました。docs/known-bugs.mdが衝突しなかったのは20分の間隔を空けて逐次コミットされたからにすぎず、同時にgit addしていたら、どちらかの追記が失われてもおかしくありませんでした。
| 時刻 | コミット | 内容 |
|---|---|---|
| 02:17:45 | 75933a0 | BUG-21修正(Chrome cold-start過剰発火) |
| 02:37:43 | 3a0907c | BUG-22修正(MS Edge conv=Eisu固着) |
| 02:47:35 | 40571ad | worktreeルールの明文化 |
わずか30分の間に、3つのコミットが積み重なっていました。危うい場面もありました。一方のセッションが変更したファイルを指定してgit addしようとしたとき、git statusにはもう一方のセッションのまだコミットしていない差分まで混ざって見えていたのです。指定を誤れば他人の作業中の変更を自分のコミットに巻き込むところでした。片方がgit checkoutやgit stash、git resetを実行していれば、もう片方の作業中のファイルを強制的に書き換えていたかもしれません。3つ目のコミットが積まれるところで踏みとどまりましたが、次も同じように踏みとどまれる保証はありませんでした。
一つの作業ツリーは、一人だけの前提で動いていた
gitという道具自体は、複数の書き手が同時に触ることを想定して作られています。ブランチを切り、変更をコミットし、後でマージする——ただしこの手続きは、それぞれの書き手が別々の作業ディレクトリを持つことも前提にしています。一つの作業ディレクトリの中で同時に2人が手を動かし、git statusやgit addをリアルタイムに共有する状況は、gitの想定の外にありました。人間が手作業で開発していた間この前提が崩れなかったのは、単に人間が同時に2つのセッションを起動しなかったからにすぎません。ルールが守っていたのではなく、偶然が守っていたのです。
この偶然は、AIエージェントが並行して複数のセッションを起動できるようになった時点で、成り立たなくなりました。git statusやgit addは、セッションを識別する仕組みを持っていないからです。
事故が起きかけた10分後、40571adというコミットで、新しいルールが書き加えられました。セッションやタスクごとに専用の作業ディレクトリと専用のブランチを用意する、というルールです。git worktree addを使うと、一つの.gitリポジトリを共有したまま作業ディレクトリだけを複数に分けられます。片方がcheckoutやresetを実行しても、別の作業ディレクトリのファイルには影響しません。ブランチも分かれているため、片方の作業がもう片方の意図しないマージ対象に混ざることもなくなります。
このルールには、線引きもありました。ユーザーが「ここで作業してほしい」と明示した場合や、一つのセッションの中で複数のタスクを順番にこなす場合には、わざわざ作業ディレクトリを分ける必要はありません。同時に動いていない限り、競合は起こらないからです。ルールを適用すべきなのは、複数のセッションが自律的に並行して動く場合に限られます。守るべき条件を絞り込むことで、「常に分けろ」という重い規約ではなく、必要な場面だけに効く軽い規約になりました。
worktreeを分けたことで、何が変わったのかを図にすると、変化の位置がはっきりします。

分かれたのは.gitではなく、作業ディレクトリの方です。オブジェクトデータベースは共有されたままなので、コミットの共有やマージは以前と変わらずできます。変わったのは、片方のcheckoutやstash、resetが、もう片方の手元のファイルにはもう届かなくなった、という一点です。
worktreeの衝突は、この章で扱う「二つの手」の一方にすぎません。もう一つの衝突は、まったく違う形で現れます。
同じキーを、二つの手が、別々の理由で退けていた
この20分間の衝突は、空間の衝突でした。同じ場所を、同じ時刻に、二つの手が書き換えようとしていたのです。もう一つの「二つの手」は、これとは違う形で現れました。同じ日には起きていません。約6週間かけて、少しずつ形を変えながら現れた衝突でした。
舞台は、TSFネイティブなアプリ(Windows Terminalなど、Text Services Framework経由でIMEと通信するアプリ)で、GJI(Google Japanese Input、Google 日本語入力)をDirectInput(直接入力)に切り替える処理です。どの仮想キーを送れば「真のIME OFF」になるかは一意には決まらず、2026年5月22日から7月2日までの5日間・6回、採用と撤回が繰り返されました。反転が起きた技術的な経緯そのものは第12章で見た通りです。ここで問うのは、なぜ反転したかではなく、反転のたびに何が記録され、何が記録されなかったかです。
最終的に収束したという結果だけを見れば、遠回りはしたが解決した、で済む話に見えます。しかし途中で何度も同じ結論に別々の手が独立にたどり着き直していた、という事実は、収束したことでは消えません。反転の中でも際立って対照的なのが、6月28日に起きた2つの撤回です。未明2時35分の098c663は、前日採用したVK_DBE_ALPHANUMERICを撤回するコミットでした。昼13時18分の9c3f11eは、別の仮想キーVK_IME_OFFへの切り替えを試み、その過程でVK_DBE_ALPHANUMERICが実は「半角英数」を意味しIMEはONのままだったという事実に行き当たります。しかしこの9c3f11eも、13時23分の668a131として数分後に撤回されました。
「なぜ戻したか」が書かれた記録と、書かれなかった記録
2つの撤回コミットを並べると、決定的な違いが見えます。098c663のコミット本文には、こう書かれていました。
TsfNative+GJI の IME OFF に VK_DBE_ALPHANUMERIC を使う変更(前コミット)がフォーカス変更時等の spurious apply_ime_open(false) を即時発火させ、GJI が予期せず直接入力モードに切り替わるリグレッションを引き起こした。F22 はコールド時 ~750ms かかるが、その遅延が spurious OFF の実害を防いでいた。
どのアプリで、何が起き、なぜ元に戻すと直るのかまでが具体的に書かれています。一方、668a131のコミット本文は、これだけでした。
This reverts commit 9c3f11e212252521b14f1a92fb33ddd9fbb73949.
git revertが自動生成する定型文だけです。9c3f11eが見つけた「半角英数(IME ON)のままだった」という事実そのものは正しかったはずですが、VK_IME_OFFへの切り替えがなぜ数分で撤回されたのかはどこにも書かれていません。新しい発見があった直後の判断が、理由を残さないまま消えていました。コミット履歴には「採用した理由」しか残らず、「却下した理由」は記録の外に置き去りにされていました。
採用の記録と、却下の記録は、別の記録だった
この反転劇を機に、2つの記録の仕組みが導入されました。一つは、試行そのものを1行ずつ記録するログです。2026年7月5日、d654f0fというコミットでdocs/experiments.mdが新設され、日付・仮説・環境・変更・観測結果・判定・コミットという7列の表に、1回の試行を1行として書き加えていきます。VK_DBE_ALPHANUMERICをめぐる反転の全経緯は、この形式で遡及的に書き起こされました。
このログには、一つ特徴的なルールがあります。判定が後日ひっくり返っても元の行は消さず、新しい行を下に足すだけです。反転そのものを履歴として残すことに意味がある、という考え方が、この「消さない」という一点に表れています。今日「正しい」と判定したことが来週覆るかもしれませんが、いつ・どんな根拠で・どちらの判定を下したかという記録さえ残っていれば、次に同じ提案が浮上したとき、以前の判定に立ち返ることができます。
534051aの行には環境欄「Windows Terminal × GJI × 約80秒idle」、観測結果欄「即時OFFにはなった」とだけ書かれ、次の行には暴発の疑いと098c663で戻した理由が続きます。短い記述でも並べて読めば、なぜ最初の判断が覆ったのかが後から来た読み手にもたどれます。
もう一つは、コミット本文そのものに対するルールです。IMEの制御やwarmup、キー選択に関わる変更をrevertするときは、どのアプリで・どのIMEの状態で・何をしたら何が壊れたかを本文に必ず書く——この3点を欠かさない規約が.claude/rules/experiment-logging.mdに加えられました。098c663は満たす良い例、668a131は満たさない悪い例として、ルールの中にそのまま引用されています。
このルールにも及ぶ範囲の線引きがあります。挙動が変わる修正のrevertにだけ適用し、リファクタやドキュメントだけのrevertには適用しません。すべてのコミットに同じ重さの記録を求めれば規約自体が守られなくなるためで、守るべき場面を絞り込む発想はworktree分離の線引きと同じでした。
なぜこの規約が必要だったかは、ルールの文中に率直に書かれています。「revert は『失敗の証拠』であり、その証拠を捨てないための規約。」失敗を記録に残さなければ、同じ失敗を、別の手が、また一から発見し直すことになります。
実験ログとrevert規約は、別々の場所に作られていますが、生まれた背景は一つです。「採用した」という記録だけでは片手落ちだという同じ反省から、俯瞰用の記録(ログ)とその場で出会う記録(revert本文)の両方が生まれました。両方があって初めて、「前にも同じことを検討した」という事実に、後から来た手が気づけます。
空間の衝突と、時間の衝突は、同じ形をしていた
2つの事件は、一見すると別の種類の失敗に見えます。worktreeの衝突は、同じ瞬間に起きた、複数の手の間の衝突でした。VK_DBE_ALPHANUMERICの反復は、隔たった時刻に起きた、過去の自分と今の自分の間の衝突でした。しかし根にある問いは同じです。「この場所を、今書き換えてよいのは誰か」「なぜ今の形になっているのか」という、所有と経緯の情報が、どちらの事件でも、暗黙のうちにしか存在していませんでした。worktree分離は空間側のこの問いに、実験ログとrevert規約は時間側の同じ問いに、それぞれ規約ではなく構造や記録として答える仕組みです。世代や出所を区別するという発想が、awaseのコードの中だけでなく、awaseを書くという行為そのものにも及んだ例だと言えます(第9章のエポック、第11章の証拠の試行帰属と同じ発想です)。
worktree分離も、実験ログも、revert規約も、破られないことまでは保証しません。.claude/rules/に置かれたルールも、docs/experiments.mdのログも、読まれて初めて効果を持ちます。読まれなければ、あるいは読んだ上で見落とされれば、区切りは紙の上にしか存在しません。その文書が実際に守られているかどうかを、機械が確かめてくれるわけではない、という一点が、まだ残っています。
他分野への転用
同じ資料を複数の人が同時に編集するツールでは、この問題はもっと日常的な形で現れます。GoogleドキュメントやFigmaが今どこを誰が触っているかをカーソルやアバターで表示するのは、空間側の区切りを規約ではなく画面上の情報として提示する工夫です。Wikipediaの「別の利用者がこのページを編集しました」という警告も同じ狙いを持っています。
時間側の区切りにも、よく知られた慣習があります。ソフトウェアの設計判断を記録するADR(Architecture Decision Record)は、採用した案だけでなく、検討した末に却下した案とその理由を書く欄を持ちます。なぜその案を選ばなかったかという記録を、コミット履歴の外に探しやすい形で残すための工夫です。
どちらの工夫にも共通する代償があります。カーソル表示にせよADRの却下理由欄にせよ、区切りを明示するための情報は誰かが手間をかけて残さない限り生まれません。記録を残す設計は、記録を書く側の規律に頼っています。規律が続く保証は、記録の仕組み自体からは出てきません。
設計原則
原則: 同じ場所を書き換える権利は、空間か時間のどちらかの軸で明示的に区切ります。
適用条件: 複数の書き手(人間とは限らず、複数のAIセッションも含む)が同じ対象に触れる可能性がある場面で働きます。実際に衝突したかどうかは問題ではありません。衝突しうる状態を、規約や注意力だけに頼って放置していないかが問われます。
実装の形: 対象が空間的に同時に触れられるか、時間的に隔たって繰り返し検討されるかで変わります。前者には、作業単位ごとに専用の場所を割り当て、構造的に交わらないようにする分離が効きます。後者には、判断を下した理由だけでなく、判断を退けた理由までを、消さずに記録として残す仕組みが効きます。
限界: この原則は区切りの手段を用意するだけで、その手段が使われ続けることまでは保証しません。作業ディレクトリを分けるルールも、revert本文に理由を書く規約も、守るかどうかは書き手の規律に委ねられています。文書は、読まれ、従われて初めて効果を持ちます。読まれなかった場合や、読んだ上で省略された場合に何が起こるかは、この原則の設計対象には含まれていません。規律が続かない場合にどうするかは、この原則の外側に残された問題です。