【ITニュース解説】Kill -9 an Agent Mid-Commit and .git/index.lock Outlives It. How Git's Locks Work
2026年11月05日に「Dev.to」が公開したITニュース「Kill -9 an Agent Mid-Commit and .git/index.lock Outlives It. How Git's Locks Work」について初心者にもわかりやすく解説しています。
ITニュース概要
Gitコミット中にプロセスが強制終了(`kill -9`など)すると、`.git/index.lock`ファイルが残り、次のGit操作ができなくなる。Gitは`kill -9`ではロックファイルをクリーンアップできないためだ。対処には、関連フックプロセスを停止後、このロックファイルを削除する。
ITニュース解説
Gitを使って開発していると、ときどき予期せぬ問題に直面することがある。例えば、git commitコマンドを実行している最中に、コンピューターの電源が落ちたり、開発環境のプロセスが強制終了されたりすることがある。次に同じリポジトリでGitの操作をしようとすると、「fatal: Unable to create '/srv/app/.git/index.lock': File exists.」といったエラーメッセージが表示され、それ以降Gitのコマンドが一切受け付けられなくなることがある。これは、Gitが作業中に一時的に作成する「ロックファイル」が、強制終了によって削除されずに残ってしまったことが原因で発生する現象だ。Gitは、このロックファイルが存在すると「他の誰かがこのリポジトリで作業中かもしれない」と判断し、データの破損を防ぐために操作を中断する。
Gitが重要なファイル(インデックス、リファレンス、設定ファイルなど)を更新するとき、データを安全に保つために特定のルールに従う。Gitは、これらのファイルを直接編集することはせず、まず元のファイルの隣に「.lock」という拡張子が付いた一時的なロックファイルを作成する。このロックファイルを作成する際、Gitは、もし同じ名前のファイルがすでに存在していれば作成を失敗させる特別な仕組みを使う。これにより、同時に複数のGitプロセスが同じファイルを更新しようとするのを防ぎ、ただ一つのプロセスだけが更新作業を進められるようにする。次に、Gitは新しい内容をこの.lockファイルに書き込む。この間、元のファイルは変更されない。新しい内容が.lockファイルに完全に書き込まれた後、Gitはこの.lockファイルを元のファイル名にリネームする。このリネーム操作は非常に重要で、オペレーティングシステムの機能によって瞬時に、つまり「アトミック」に行われる。これにより、他のプロセスが、更新途中の中途半端な状態のファイルを見ることを防ぎ、常に完全に古い状態か、完全に新しい状態のどちらか一方だけが見えるように保証される。もし何らかの理由で更新が途中でキャンセルされた場合は、作成途中の.lockファイルを削除するだけで、変更がロールバックされ、元のファイルは何も変更されずに安全に保たれる。
Gitは、通常、自身が作成したロックファイルを適切にクリーンアップする仕組みを備えている。たとえば、Ctrl+CでGitコマンドを中断したり、killコマンドでプロセスを停止させたりした場合には、Gitは終了前に作成したロックファイルを自動的に削除する。これは、Gitプロセスがこれらのシグナルを受け取った際に、登録されている特定の処理(シグナルハンドラ)が実行され、クリーンアップが行われるためだ。しかし、kill -9コマンドのように「SIGKILL」というシグナルを使ってプロセスを強制終了した場合、話は変わってくる。SIGKILLは、オペレーティングシステムのカーネルによってプロセスを即座に停止させるため、プロセスが自分で設定したクリーンアップ処理(シグナルハンドラ)が実行される機会が全くない。このため、kill -9でGitプロセスが停止すると、Gitが作成していた.lockファイルがディスク上に残されてしまうのだ。Dockerコンテナの停止時など、特定の状況では最終的にSIGKILLが使われることがあり、同様にロックファイルが残る原因となることがある。
残された.git/index.lockファイルは、一見すると心配になるが、ほとんどの場合、リポジトリのデータが失われることはない。前述の通り、アトミックなリネーム処理が完了していないため、実際の.git/indexファイルは、Gitプロセスが強制終了される前の状態を保っている。つまり、kill -9されたcommitコマンドが作成しようとしていた変更内容は、インデックスにはまだ反映されていない。このような状況に遭遇した際の一般的な対処法は、rm .git/index.lockコマンドでロックファイルを削除することだ。これにより、Gitは再び操作を受け付けるようになる。ただし、ここで一つ注意が必要だ。git commit中に起動されていたpre-commitフックなどのスクリプトが、Git本体のプロセスが強制終了された後も、孤立したプロセスとしてバックグラウンドで動き続けている可能性がある。これらの孤立プロセスがまだリポジトリのファイルにアクセスしようとしている場合、単純にロックファイルを削除するだけでは問題が完全に解決しないことがある。そのため、ロックファイルを削除する前に、関連する可能性のある孤立したプロセスを探し出し、それらを先に停止させてから、ロックファイルを削除する手順が最も安全である。その後、git statusで状態を確認すると、強制終了されたコミットの変更がまだステージされていないが、作業ツリーには残っていることがわかるだろう。
Git 2.54以降のバージョンでは、残されたロックファイルの診断を助けるための便利な機能が追加された。git config --global core.lockfilePid trueという設定を有効にすると、Gitはロックファイルを作成する際に、そのロックを作成したプロセスのID(PID)を記録したファイル(例: index~pid.lock)も同時に作成するようになる。もしロックファイルが存在するためにGitの操作が失敗した場合、GitはこのPIDファイルを参照し、記録されたPIDのプロセスがまだ実行中かどうかを確認する。もしプロセスがすでに終了していると判断できれば、「Lock was held by process N, which is no longer running; the lock file appears to be stale.」といった、より具体的なメッセージが表示される。これにより、ロックファイルが本当に過去の強制終了によるものなのか、それともまだ生きているプロセスによって使用されているのかを判断しやすくなり、適切な対処につながる。ただし、この設定を有効にしても、Gitが自動的にロックファイルを削除してくれるわけではないため、手動での削除は依然として必要だ。
Gitのワークツリー機能を使うと、一つのリポジトリから複数の作業ディレクトリを派生させ、それぞれ独立したブランチで並行して作業を進めることができる。このとき、それぞれのワークツリーは、独自のindexファイルと、それに紐付くindex.lockファイルを持つ。そのため、異なるワークツリーで同時にgit commitのような操作を行っても、index.lockファイルが原因で競合が発生することはない。一つのワークツリーでindex.lockが残ったとしても、他のワークツリーの作業には影響しない。しかし、Gitのリファレンス(ブランチやタグの参照)、packed-refs(パックされた参照)、configファイルなどは、すべてのワークツリーで共有される。これらの共有ファイルに対する操作(例: git fetch、git push -u、git gcなど)では、ロックの競合が発生する可能性がある。例えば、複数のワークツリーで同時にgit fetchを実行すると、refs/remotes/origin/main.lockのような共有される参照のロックファイルで衝突することがある。もし共有されるファイルに対応するロックファイルがスタックしてしまった場合、それはそのファイルを使用するすべてのワークツリーのGit操作をブロックしてしまうことになる。このような競合を防ぐために、Gitはcore.filesRefLockTimeoutやcore.packedRefsTimeoutといった設定を提供しており、ロックの取得をリトライする時間を調整することで、一時的な競合を緩和できる場合がある。
Gitのリファレンス管理には、従来の「ファイル」バックエンド(1リファレンス1ファイル)に加えて、「reftable」という新しいバックエンドも存在する。reftableは、複数のリファレンスを一度にアトミックに更新できるなどの利点がある。しかし、reftableバックエンドでは、リファレンスの書き込み操作は本質的に単一スレッドであり、tables.list.lockという単一のロックを必要とする。このため、reftableを使用しているリポジトリでは、異なるワークツリーで異なるブランチにコミットする場合でも、この単一のロックファイルで競合が発生しやすくなる可能性がある。もしtables.list.lockがスタックした場合、そのリポジトリ全体のリファレンス更新が停止してしまうことになる。
このように、Gitのロックファイルは、リポジトリのデータの一貫性を保つための重要な仕組みだ。kill -9などの強制的なプロセス終了がロックファイルを残してしまうことがあるが、それはデータが失われたことを意味するわけではない。この状況に遭遇した際は、孤立したプロセスを特定して停止させ、その後にロックファイルを安全に削除することが適切な対処法となる。また、複数のワークツリーを使う際には、indexロックは独立しているが、共有されるファイルについてはロックの競合が発生する可能性があることを理解しておくことが重要である。Gitのロックメカニズムとその動作を理解することは、システムエンジニアとしてGitを安全に、そして効率的に使いこなす上で非常に役立つ知識となるだろう。