09-19 の朝、サイトのリポで git log を見たら、自分がまだコミットしていない原稿 3 本と図 4 枚が、別のセッションの commit に入って origin に届いていた。commit したのは同じ PC で走らせていたもう 1 つの Claude Code セッションで、当方の作業コピーをそのまま共有していた。
「同じリポで 2 つ走らせるな」は知っていました。守っていなかったのは、それがファイルの話ではなく index の話だと分かっていなかったからです。この記事は、起きた 2 例を git の写しで数え、「自分のファイルだけ git add すれば安全」が成り立たないことを捨てリポで 30 秒で再現し、今日から入れられる 3 段を並べます。
この記事の位置づけ(2026-09-26 追記)
- 既に知られていること: 同じ作業コピーでは index(stage)も共有され、分けたいときは
git worktreeを使うこと(git の文書)。 - この記事で新しく示すこと: AI のセッションを 2 つ同じ作業コピーで動かしたときに実際に起きた 2 例と、
git commit --onlyで防げる範囲の最小再現。 - 当てはまる範囲: 同じ作業コピーを 2 つのプロセスが触る場合全般。事故の件数は当方の 2 例だけです。
- 言葉: 本文の「門」=公開や commit の前に自動で止めるチェック、「器」=点検や処理のための自作スクリプト、「毒」=チェックが効くか確かめるためにわざと壊した入力のことです。
数字の元データと再現の手順は、検証の記録(Lab)にまとめています。
1 例目: 他方の 1 commit に、自分の 24 本が乗っていた
他方の commit を git show --stat で開くと 44 files。パスで分類して CSV から数えると、当方の未コミット 24 本(本文 3・図 4・証拠 17)と他方の 20 本でした。他方が git add -A を打ったのか . なのか個別なのかは、git の履歴には残りません。最初は「-A が巻き込んだ」と書いていて、外部の精査で「それは残らない」と指摘され、「他方の commit に乗った」まで下げました。
2 例目: push していない commit が origin に居た
2 日後。当方は docs だけを commit(12:23:27)して push はしていない(当方の転写に git push は 0 件)。ところが 15 秒後にできた他方の commit(12:23:42)と同じ枝で、当方の commit が git branch -r --contains に出た。残っている事実はここまで=「当方は push していない」「その後 origin に含まれていた」。どの push で上がったかは git に残らないので、「他方の push に乗った」とは書けません。
commit の author で見分けようとしましたが、同じ PC・同じ git config なので 3 commit とも %an は同名でした。
30 秒で再現できる: git add a.txt でも b.txt が乗る
捨てリポで次の状態を作ります。他方が b.txt を変えて git add b.txt(stage だけ)、当方が a.txt を変える。ここで当方が「自分のファイルだけ」add して commit すると——
git add a.txt; git commit -m x → 2 files changed (b.txt も乗る)
git commit --only -- a.txt -m x → 1 file changed (b.txt は stage に残る)
3 回やって 3 回同じ。git add a.txt は a.txt を index に足すだけで、index に既に居る b.txt はそのまま commit に入ります。同じ作業コピーはファイルだけでなく index も 1 つなので、「自分のパスだけ add した」は「他方が stage したものも一緒に commit した」と同じ意味になります。
git commit --only -- <path> は index を無視して指定パスだけを commit する Git 公式の口で、これなら乗りません。ただし他方の stage は残ったままです。
表にすると
| 例 | 何が起きたか | 数 |
|---|---|---|
| 1 例目(09-19) | 他方の 1 commit に当方の未コミットが乗って origin へ | 44 files 中 当方 24・他方 20 |
| 2 例目(09-21) | 当方は push していないのに origin に含まれていた | 12:23:27 → 12:23:42=15 秒・--contains=origin/master・%an 3/3 同名 |
再現 ①git add a.txt; git commit
|
他方が stage 済みの b.txt も乗る | 2 files(3/3) |
再現 ②git commit --only -- a.txt
|
a.txt だけ | 1 file(3/3)・b.txt は stage に残る |
| 制限の門 | 予定内/別 dir/同 dir の別ファイル/--dir 明示/.bak/リポの外 |
exit 0/1/1/0/1/2 |
2 例とも、commit の前に git status -sb か git diff --cached --name-only を読んでいれば他方の変更は表示されていたはずです(当時は読んでいないので推論)。
今日から入れる 3 段
-
隔離——セッションごとに
git worktree addで working tree と index を分ける。これが第一選択で、以下は分けられない日の話。 -
制限——commit に乗るものを機械で絞る。
git commit --only -- <path>を使い、さらに commit 前に「stage に予定外のパスが在れば赤」の門を置く。当方の門はstaged_path_gate.py --check <自分のパス…>=git diff --cached --name-only -zを取り、引数のパス(完全一致・ディレクトリは--dirで明示)と比べて 1 本でも予定外なら exit 1、git が失敗したら exit 2。 -
監査——
git add -Aを打たない。commit の前後でgit status -sbとgit diff --cached --name-onlyを読む。push していないはずの commit が origin に居るかはgit branch -r --contains <sha>。
チェックの最初の版にあった穴 2 つ
この門は書いた日のうちに外部の精査で 2 か所直しています。1 つ目、予定のパスを .git/ の中の 1 ファイルに保存していた=同じ作業コピーを共有する他方が上書きできる(「共有が危ない」という記事の道具が共有されていた)。2 つ目、先頭一致だったので同じディレクトリの別ファイル(docs/other.md・docs/mine.md.bak)が通っていた。予定は毎回引数で渡し、ファイルは完全一致を既定にして、毒 6 本で exit 0/1/1/0/1/2 を確かめました。
もう 1 つ。1 例目の件数を最初は手で書いていて、CSV から数え直したら 2 か所違っていました。本文の数は全部 CSV 由来です。
言えないこと
他方も同じ運用者の別セッションで、第三者ではありません。門が止めるのは stage の巻き込みだけで、同じファイルを他方が上書きするのは止められない(そこは worktree の領域)。「防げた」とは書きません。最小再現は「他方が stage だけ」の型で、1 例目の「他方が commit まで進めた」型とは向きが違い、再現しているのは index の共有そのものです。worktree に分けたあとに残る問題(deploy・共有 remote・生成物の未コミット)は数えていません。
再現の器と門は https://github.com/sumitsuke/staged-path-gate に置きました(MIT)。
AI の利用について
2 例を踏んだのは AI セッション自身で、git の写し・分類・再現の器・門・この下書きも AI(Claude Code)が作っています。数字は git の出力から写し、パスは匿名 ID に落とし、原本は非公開の箱に SHA256 つきで置いています。
関連
- 本家(匿名化した分類表・時系列・再現ログ・門の毒テスト): https://sumitsuke.jp/via/qiita/lab/two-sessions-one-working-copy/
- 文書に書いたルールは守られない——発火点を器に置く: https://sumitsuke.jp/via/qiita/lab/rule-placement-document-vs-gate/
Sumitsuke は、AI や外注で作ったものの点検と修理、業務自動化、検証を受託しています。AI に任せた作業の「何が起きたか」を git と数で検収する設計は → https://sumitsuke.jp/works/verification/
この記事は Zenn にも同じ内容を掲載しています。
