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#28241、libgit2#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 をたまに眺めておくと安心できる。
先行事例と、この記事の位置づけ
- openai/codex#28241 / libgit2#7297: tree型refがlibgit2系クライアントを壊す報告(2026年6月)
- openai/codex#28847: SourceTreeのブランチ一覧とフェッチが壊れる報告
- Atlassianコミュニティ: 同症状のQ&A(2026年6月)。公式チケットはSRCTREEWIN-14733
- Zennの体験記: 日本語の先行記事(2026年7月)。同じ幽霊バッジ(1035↓)に遭遇していて、対処はrefs/codexフォルダ削除
既存の報告はどれも「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の削除はデータを失うので二番手でいい