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?

SourceTreeに突然大量の差分が!何をしても消えない幽霊バッジの犯人は、Codexが残した一本のgit refだった

0
Posted at

TL;DR

  • Windows版SourceTreeで、ahead/behindバッジが異常値のまま固まり、ローカルブランチの大半がサイドバーから消えた
  • git本体は完全に正常だった。SourceTreeが .git/packed-refs の古いスナップショットだけで画面を作っていた
  • 引き金はOpenAI Codexが .git/refs/codex/turn-diffs/checkpoints/ 配下に作るref。うちの環境ではフルパスが261文字で、Windowsの伝統的な上限260文字を超えていた
  • git pack-refs --all 一発で直った。先行事例でよく紹介されている「refs/codexフォルダを削除」と違って、Codex側のデータを消さずに済む

なお調査はClaude Codeに手伝ってもらった。というより犯人特定はほぼやってもらった。

起きたこと

ある日SourceTreeを開いたら、developブランチに「1017↓」が付いていた。1017コミットもリモートに置いていかれた覚えはない。しかもグラフ上ではdevelopとorigin/developが同じコミットを指している。矛盾している。

フェッチしてもプルしても消えない。プルは取り込むものが無いと言うだけ。タブを開き直しても、SourceTreeを再起動しても、PCを再起動しても、SourceTreeを最新版にアップデートしても、1017↓はそこにいた。

もっと気持ち悪い症状もあった。ローカルブランチは27本あるはずなのに、サイドバーには4本しか出ていない。試しにSourceTree自身の機能で新しいブランチを作ってみたら、作成もチェックアウトも成功するのに、そのブランチがサイドバーに現れない。自分で作った子を自分で認識できていない。

まずgitの切り分け

コマンドラインで確認すると、gitとしては何も起きていなかった。

$ git rev-list --left-right --count develop...origin/develop
0       0

$ git status -sb
## develop...origin/develop

差分ゼロ。追跡設定も正常。この時点でリポジトリ破損の線は消えて、「SourceTreeの表示だけが壊れている」ことが確定した。ちなみにSourceTree内蔵のgit(2.20系)で同じコマンドを打っても結果は同じだったので、gitバイナリの新旧問題でもない。

決定打はpacked-refs

.git/packed-refs を開いたら全部つながった。

packed-refsはrefの一括スナップショットファイルで、git gc などが定期的に書く。個別ファイル(ルーズref)が存在する間はそちらが優先されるので、packed-refs側の値が古くても普通は問題にならない。

そのpacked-refsに、こう書いてあった。

  • refs/heads/develop の値が3週間前のコミットのまま
  • その古い値と現在のdevelopの差を数えると、進み0・遅れ1017。バッジの数字と完全一致
  • packed-refsに記録されたローカルブランチはちょうど4本。サイドバーに生き残っていた4本と完全一致
  • 消えたブランチと新規作成したブランチはルーズrefとしてしか存在しない

つまりSourceTreeはルーズrefの読み取りに失敗していて、packed-refsの古いスナップショットだけで画面を組み立てていた。グラフ本体は git log の出力から作るので正しく、サイドバーとバッジだけが過去の世界にいた、という構図。

# packed-refsに記録された古い値を見る
$ grep ' refs/heads/develop' .git/packed-refs
bbbbbbbb... refs/heads/develop        # 3週間前の値

# 古い値と現在のdevelopの差を数える
$ git rev-list --left-right --count bbbbbbbb...develop
0       1017                           # バッジと一致。これが幽霊の正体

犯人のref

ではなぜルーズrefが読めないのか。find .git/refs -type f の出力に、1本だけ明らかな異物がいた。

.git/refs/codex/turn-diffs/checkpoints/<64桁hex>/<64桁hex>/<13桁epoch-ms>/<UUID>

OpenAI Codex(CLI / VS Code拡張)がセッション中のチェックポイントとして作るrefらしい。このepochミリ秒を日時に直すと、症状が出始めた日の夜と一致した。

そしてパスを数えると、固定部分だけで219文字ある。

.git\refs\codex\turn-diffs\checkpoints\   → 39文字
<64桁hex> \ <64桁hex> \ <13桁> \ <UUID36桁> → 180文字

リポジトリルートのフルパスが42文字以上なら、合計でWindowsの伝統的な上限MAX_PATH(260文字)を超える。C:\Users\<ユーザー名>\Documents\<リポジトリ名> 程度で普通に超える。うちは261文字だった。

壊れ方の推定メカニズム

SourceTreeがルーズref読み取りに失敗する直接の理由は、ソースが読めないので確定できていない。候補は2つある。

1つ目はMAX_PATH説。SourceTreeは.NET Framework製で、260文字超の作業ファイルを扱えない前科がある(公式トラッカーのSRCTREEWIN-7075)。今回の実測261文字はぴったり境界を跨いでいる。

2つ目はtree型ref説。このcheckpoint refはcommitではなくtreeオブジェクトを直接指しているそうで、libgit2ベースのクライアント(TortoiseGitなど)が「the requested type does not match the type in the ODB」で落ちる報告が既にある(openai/codex#28241libgit2#7297)。

ただ、後述の修復後は「refがtree型のままpacked-refsの中に存在する」状態でSourceTreeが完全に正常化した。少なくともうちの環境では、refの存在自体や型ではなく、ルーズファイルとして存在することがトリガーだったように見える。

修復: git pack-refs --all

git pack-refs --all

これだけ。全refを現在の正しい値でpacked-refsにまとめ直す標準のメンテナンスコマンドで、git gc が内部でやっていることの一部を明示的に呼ぶだけ。ブランチの値も履歴も一切変わらない。

これで何が起きるかというと、

  • packed-refsが最新値で書き直される(古いdevelopの記録が消える)
  • 問題の長パスなルーズファイルが畳み込まれて消える(refそのものはpacked-refs内に残る)

の2つが同時に達成される。実行後にSourceTreeを完全に再起動したら、27本のブランチが全部戻り、幽霊バッジも消えた。

# 検証
$ grep -E ' refs/heads/(develop|test-1017)$' .git/packed-refs
aaaaaaaa... refs/heads/develop         # 最新値になった
aaaaaaaa... refs/heads/test-1017       # 見えなかった新規ブランチも記録された

$ find .git/refs -type f
.git/refs/remotes/origin/HEAD          # これだけ残るのは正常

先行事例では「.git/refs/codex フォルダを削除する」対処が定番になっている。それでも直るが、Codexのチェックポイント(セッションの復元ポイント)を道連れにする。pack-refsならデータを一切失わずに済むので、こちらを先に試す方がいいと思う。

再発について

根本原因はCodex側にあり、該当issueはこの記事の時点でopenのままらしい。なので次にCodexを使えばまたルーズrefが作られて、たぶん再発する。再発しても同じコマンドで直る。

あとCodexのcheckpoint周りには、大きなファイルを含むプロジェクトでgitオブジェクトが際限なく溜まってディスクを100GB以上食った報告もある(openai/codex#29388)。Codexを使っているリポジトリでは git count-objects -vH をたまに眺めておくと安心できる。

先行事例と、この記事の位置づけ

既存の報告はどれも「refs/codexを消せば直る」で終わっている。この記事の追加分は、幽霊バッジの数字の正体をpacked-refsの古いスナップショットとの差分として数値で特定したことと、データを消さない git pack-refs --all という修復方法の2点。

まとめ

  • GUIクライアントの表示とgit本体の状態が食い違ったら、まず git rev-list --left-right --count で本体をシロ判定する
  • シロなら .git/packed-refs とルーズrefを見比べる。「画面に生き残っているブランチ一覧 = packed-refsの中身」なら、ルーズref読み取りが死んでいる
  • find .git/refs -type f で異物を探す。AIコーディングエージェントが .git に何かを書き残す時代なので、見慣れない名前空間(refs/codexなど)は疑っていい
  • 直すのは git pack-refs --all 一発。refs/codexの削除はデータを失うので二番手でいい
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?