0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

未コミットの変更が消える:並行セッションとgit addの空振り

0
Posted at

はじめに

同じリポジトリを、複数のAIエージェントのセッションで同時に触っていました。
リポジトリ内のMarkdownファイルにメモを1件追記して、そのまま次の作業へ移ります。
しばらくして、さっき自分が足したはずの追記を探したら、跡形もなく消えていました。

このとき僕は、同じメモを1つの作業のなかで2回も失いかけています。
1回目は「書いたのに消えた」。
2回目は「コミットしたのに中身が入っていなかった」。

この記事は、その2つの失敗の再現条件と原因、そして塞ぎ方の記録です。

こんな人に向けて書いています

  • 同じリポジトリを、複数のAIエージェントのセッションで並行して触っている人
  • gitのworktree(同じリポジトリから複数の作業フォルダを作る機能)を使って並行作業をしている人
  • 「ファイルに書けた=残った」だと思っている人

この記事でわかること

  • 未コミットの変更を静かに捨てるgitコマンドと、逆に守ってくれるコマンドの違い
  • git add がエラーを出さずに空振りする条件と、そのとき何が起きているか
  • 隙間を短くする手順と、コミットの中身を実地で確認するコマンド

前提

  • gitを使い、同じリポジトリの作業ツリーを複数のセッションが同時に触る状況を想定しています。

起きたこと:書いたはずのメモが消えていた

やっていたのは、リポジトリ内のMarkdownファイルにネタを追記する運用です。
記録用の手順経由でエントリを1件追記し、そのまま次の作業に移りました。
次の作業は、ドキュメントの整合チェックです。

その整合チェックの途中で、さっき足したエントリを索引に載せようとしました。
しかし、エントリが見つかりません。
たどっていくと、追記が丸ごと消えていました。

並行運用中のどこかで、作業ツリーがHEADへ戻されていました。
その時点で、まだコミットしていなかった追記も消えていました。

正直に言うと、どのコマンドが直接の引き金だったかまでは特定できていません。
ただ、消え方から言えることははっきりしています。

前提知識:捨てるコマンドと、守ってくれるコマンド

tracked file(gitがすでに管理下に置いているファイル)への未コミットの変更は、巻き戻し系のコマンドで捨てられることがあります。
たとえば、並行セッションが git reset --hard や git checkout -- <file> を実行した場合です。
このとき、警告が出ないまま変更が消えることがあります。

git stash は変更を捨てるコマンドではなく、退避するコマンドです。ただし作業ツリーからは消えるので、退避先(git stash list)を知らなければ、消えたのと同じに見えます。

ここで1つ、自分の勘違いを正しておきます。
最初は「並行マージのせいで上書きされた」と思っていました。
しかし、これは違いました。

git merge や git pull、git checkout <ブランチ> は、この手の未コミット変更をむしろ守ってくれます。
上書きしそうならabortして守る、というのを実測で確認しました。

だから怖いのはマージそのものではありません。
巻き戻し系のコマンドが並行して走る状況の方でした。

いつ起こるか

gitで管理しているファイルにコミットしないまま変更を書きます。
その状態のまま、別のセッションが作業ツリーを巻き戻すコマンドを実行すると起こります。

他に起こりえる場面

同じ原理なので、引き金になるコマンドが違っても結果は同じです。

  • 別のセッションが git reset --hard を実行したとき
  • 別のセッションが git checkout -- <file> でファイルを戻したとき
  • 別のセッションが git stash を実行したとき

困りポイント

  • 警告もエラーも出ないので、消えた瞬間には気づけない
  • 直接の引き金は特定できていない

「ファイルに書けた」と「gitに残った」は、まったく別です。
コミットしていない追記は、いつ巻き戻されてもおかしくない状態でした。

2回目:復旧のコミットが、また空振りする

幸い、追記した本文は退避データに残っていたので、そこから復元しました。
退避データから復旧して git add して git commit しました。
しかし、そのコミットには索引2本しか入っていませんでした。

念のためコミットの中身を確認しました。
--stat には 2 files changed としか出ていません。

入っていたのは索引2本だけです。
復旧したはずのネタ帳は、コミットに入っていませんでした。

同じメモを、2回目もまた取りこぼしていました。

原因は、1回目とは少し違います。
手順を並べると、こうなります。

  1. 退避データの中身をネタ帳に追記する
  2. ここで数手ぶんの時間が空く
  3. git add ネタ帳 する

この2と3のあいだに、また並行セッションが作業ツリーをHEADへ戻しました。
すると git add は、追記が消えた後のファイルを見ます。
そして「変更なし」と判断します。

しかも、git add はここでエラーも警告も出しません。
「何も無かった」を、静かな成功として受け入れます。

だからコミットも普通に作られます。
しかし、中身は入っていません。

--stat の 2 files changed を見て、ようやくネタ帳が入っていないと気づきました。

代理指標は「コミットが作られた」ことです。
本物は「追記が実際に入った」ことです。

このときは代理指標だけを信じて、本物を確認していませんでした。

いつ起こるか

ファイルへの書き込みと git add のあいだに時間が空きます。
その隙間で作業ツリーが巻き戻されると起こります。

困りポイント

  • git add がエラーを出さないので、空振りしても気づけない
  • コミットは普通に作られるため、成功扱いのまま先に進んでしまう

手順:どう直したか

やったことは2つです。

1. 追記からpushまでの隙間を短くする

追記とaddを別々のタイミングで打つと、あいだに数手ぶんの時間が空きます。
そこで、追記→add→commit→pull --rebase→pushを1コマンドに連結しました。

ただし、1コマンド化しても排他にはなりません。
並行セッションが同じ作業ツリーを操作すれば、割込みの可能性は残ります。

確実に防ぐには、セッションごとに作業ツリーを分離するか、排他手順を設けます。
今回の1コマンド化は、あくまで割込みの隙間を短くする対策です。

2. コミットした後、本当に入ったかを実地確認する

「コミットできた」で満足しません。
コミット済みのファイルの中身を直接確認します。
追記が入っているかを、目で確かめます。

図解:隙間で起きる空振りと、確認まで行う手順

失敗しやすい流れ
追記
  ↓
(並行セッションの巻き戻し)
  ↓
git addが空振り
  ↓
狙ったファイルが入らないコミット

確認まで行う流れ
追記
  ↓
即時add・commit
  ↓
git showでコミットの中身を確認

コード例

確認に使うのはこの1行です。
作業ツリー側ではなく、コミットの中身を読ませているのがポイントになります。

# HEADに実際に入っているか(作業ツリーではなくコミットの中身を見る)
# README.md の部分を、確認したいファイルのパスに変えて使う
git show HEAD:README.md

HEADへの反映確認では、作業ツリーではなく git show HEAD: にファイルのパスを付けて、コミットの中身を見ます。

1回目の失敗は、作業ツリーの側が巻き戻されたことで起きました。
2回目は、作業ツリーが巻き戻された結果、コミットに入らなかったことで起きました。

どちらも、信用できるのはコミットに入った実物だけです。

この確認で、空振りを早く検知できるようにしました。

仕組みに焼き込む

一番大事なのは、これを「次から気をつける」で終わらせなかったことです。
人やAIの記憶に頼らず、手順そのものに焼き込みました。

僕は記録用の作業手順そのものに、「追記したら、その場でコミットしてpushするところまでが1セット」という工程を足しました。
手順の側に「コミットしていない記録は、記録していないのと同じ」と書いておきます。

そうすれば、次の自分や別のセッションの自分が、同じ穴に落ちるのを防ぐ手がかりになります。

結局どこを直したか

  • gitのtracked fileへの未コミットの変更は、reset --hard や checkout -- のような巻き戻し系のコマンドを並行セッションが実行すると静かに消える。stash は退避するだけだが、退避先を知らなければ同じように見える。逆に merge / pull / checkout <ブランチ> は、上書きしそうならabortして守ってくれる。書けた ≠ 残った。
  • git add は、変更が無くてもエラーを出さずに成功する。だから「コミットが作られた」は「中身が入った」の保証にならない。git show HEAD: にパスを付けて実物を見るまで信じない。
  • 対策は、追記からpushまでの隙間を短くすること、作業ツリーを分離するか排他手順を設けること、結果を実地で確認すること、そしてそれを手順に焼き込むこと。

複数のAIエージェントで同じリポジトリを回す運用は、これからもっと普通になると思います。
そのとき「ファイルに書いた」を成功と信じると、静かにメモが溶けていきます。
コミットするまでは、まだ何も残っていません。

同じメモを1セッションで2回失いかけて、「書いた」と「残った」は別だと突きつけられました。

Miyoki Labsでは、AIエージェントと人間の協働に関する実践知を発信しています。
詳しくは、公式サイトをご覧ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?