Claude Codeでgit replaceが効かないworktreeの原因と回避策
Claude Codeが作ったworktreeでは、git replaceで差し替えたファイルが置換前の内容になります。原因はGIT_NO_REPLACE_OBJECTS=1でのチェックアウトで、回避はworktreeを手動で作ることです。
git replace で差し替えたオブジェクトがあるリポジトリで claude --worktree を使うと、worktreeのファイルは置換後ではなく置換前の内容で出てきます。git show HEAD:<パス> が返す内容と、Claude Codeのworktreeにあるそのファイルの中身が食い違う状態です。
原因は、Claude Codeがworktreeのファイルをチェックアウトするgitコマンドを GIT_NO_REPLACE_OBJECTS=1 付きで走らせることです。自分のシェルでこの変数を export しても、その値は無視されます。回避は1つで、git worktree add でworktreeを自分で作り、その中で claude を起動します。
どんなときに起きるか
条件は2つだけです。リポジトリが refs/replace/ に置換エントリを持っていること。そして、そのリポジトリで --worktree や EnterWorktree ツールなど、Claude Codeにgitでworktreeを作らせていること。
git replace を普段使わないなら、この記事の症状には当たりません。使っている側のよくある場面は、巨大なファイルや壊れたオブジェクトを履歴を書き換えずに別物へ差し替えているケースです。
症状の見分け方
git show HEAD:<パス>の出力と、worktreeにある同じパスのファイルの中身が違う- 置換を入れたあとにチェックアウトし直したメインや、自分で
git worktree addしたworktreeでは、git showと同じ置換後の内容が出る git replace -lに1行以上出る- worktreeを自分で
git worktree addした場合は、置換後の内容が出る
手元の小さなリポジトリで再現する
仕組みを確かめるため、使い捨てのリポジトリで同じ状況を作ります。app.txt の中身 original を、git replace で replaced に差し替えます。Gitは2.50.1(Apple Git-155)です。
git init -q -b main repo && cd repo
echo original > app.txt && git add . && git commit -qm c1
ORIG=$(git rev-parse HEAD:app.txt)
NEW=$(echo replaced | git hash-object -w --stdin)
git replace "$ORIG" "$NEW"
git replace -lgit replace -l には置換元のオブジェクトIDが1行出ます。この状態で、通常のgitで作ったworktreeと、GIT_NO_REPLACE_OBJECTS=1 を付けて作ったworktreeを比べます。後者がClaude Codeの作り方に相当します。
git worktree add -q ../wt1 -b wt1
GIT_NO_REPLACE_OBJECTS=1 git worktree add -q ../wt2 -b wt2
cat ../wt1/app.txt ../wt2/app.txt出力は replaced、original の順でした。通常のgitは置換後を書き出し、変数を付けたほうは置換前を書き出します。
もう1つ気づいたのは、GIT_NO_REPLACE_OBJECTS を付けずに作ったほうのworktree(wt1)で git status を見ると app.txt が変更扱いになったことです。インデックスには置換前のオブジェクトIDが入っているのに、作業ツリーには置換後の内容が出ているためだと考えられます。小さな再現での観察なので、実際のリポジトリで同じ表示になるとは限りません。
Claude Codeはなぜ置換を無視するのか
worktreeページのトラブルシューティング節には、Claude Codeがworktreeのファイルをチェックアウトするgitコマンドを GIT_NO_REPLACE_OBJECTS=1 で実行し、利用者が export した値も無視すると書かれています。
置換を無視する理由は、ドキュメントには書かれていません。変更履歴には、v2.1.309で「Claude Codeが作るworktreeとブランチのチェックアウトが、git replace refsを無視するようになった」という修正が載っています。同じ項目に「ほかのgit操作はもともと無視していた」ともあり、worktreeの作成が他の操作に揃えられた形です。
v2.1.309のリリースノートにも、同じ修正が1行で載っています。このバージョン以降のClaude Codeが対象です。
この挙動は不具合ではなく、Claude Codeのgit操作を置換なしで統一した結果です。ドキュメントも、トラブルシューティングに回避策つきで載せています。全体像はworktreeの実践ガイドにまとめています。
回避策: worktreeを手動で作ってから起動する
置換後の内容が必要なら、worktreeをClaude Codeに作らせず、git worktree add で自分で作ります。ドキュメントの手順そのままです。
手動worktreeでClaude Codeを起動する
- 1
worktreeを作る
新しいブランチを切って、リポジトリの外に作ります。既存ブランチを使うなら
-bを外し、git worktree add ../project-bugfix fix-issue-456の形でブランチ名を渡します。 - 2
その中でclaudeを起動する
作ったworktreeに移動して
claudeを実行します。Claude Codeはすでにあるworktreeの中で動き、チェックアウトは自分のgitが済ませています。 - 3
終わったら取り除く
git worktree removeにworktreeのパスを渡して削除します。一覧はgit worktree listで確認できます。
git worktree add ../project-feature-a -b feature-a
cd ../project-feature-a
claude起動前に、置換後の内容が出ているかを確かめておくと安心です。git replace -l の対象になっているファイルについて、git show HEAD:<パス> の出力と、worktree内のそのファイルを cat した中身が揃っているかを見ます。
手動で作ると変わること
手動のworktreeでは、claude --worktree が自動でやってくれる仕事を自分で引き受けます。
| 項目 | claude --worktree | 手動の git worktree add |
|---|---|---|
| 置き場所 | claude --worktree.claude/worktrees/<name>/ | 手動の git worktree add好きな場所(リポジトリの外も可) |
| ブランチ名 | claude --worktreeworktree-<name> | 手動の git worktree add自分で指定 |
| 置換後の内容 | claude --worktree置換前になる | 手動の git worktree add置換後になる |
.worktreeinclude のコピー | claude --worktreegit経由で作ったworktreeに自動 | 手動の git worktree addドキュメントには記載なし |
| 終了時の掃除確認 | claude --worktree終了時にClaude Codeが確認 | 手動の git worktree add自分で git worktree remove |
.worktreeinclude の行は、ドキュメントが「Claude Codeがgitで作るworktree」に適用すると書いている点に基づきます。手動のworktreeで .env が必要なら、自分でコピーします。仕組みはworktreeincludeで.envをコピーする書き方にあります。
一方で、プラグイン・Bashの許可ルール・.claude/skills の読み込みは、ドキュメントによると --worktree で作っても git worktree add で作っても同じです。手動に切り替えても、そこは失われません。
手元のリポジトリが置換を使っているか確かめる
自分のリポジトリが当てはまるかは、2つのコマンドで分かります。
git replace -l
git for-each-ref refs/replaceどちらも何も出さなければ置換は無く、この記事の症状は起きません。git replace -l は置換元のIDを1行ずつ出します。git for-each-ref では refs/replace/ に続く名前が置換元で、左端のIDが差し替え先です。
次に、worktreeの中のファイルが置換前なのか置換後なのかを見分けます。通常のgitと、置換を無視するgitの出力を並べて比べるのが早い方法です。
OID=$(git ls-files -s app.txt | awk '{print $2}')
git cat-file -p "$OID"
git --no-replace-objects cat-file -p "$OID"手元の再現では、1行目が replaced、2行目が original でした。worktreeの app.txt が2行目と同じなら、Claude Codeが置換なしで展開した状態です。
見つかった置換エントリをどうするかは、リポジトリごとの事情で変わります。置換を残したまま手動worktreeで作業する道と、置換エントリを git replace -d で外してふつうの履歴に戻す道があります。後者は他の利用者のチェックアウトにも影響するため、チームで共有しているリポジトリでは合意が要ります。
回避策を選ぶ前に考えておきたいこと
置換後の内容で作業したいのか、それとも履歴上の本来の内容で作業したいのかを、先に決めておくと判断が早くなります。
置換を入れている目的が「手元の作業ツリーで差し替え後のファイルを読み書きしたい」ことなら、手動のworktreeが合います。逆に、Claude Codeが編集した結果をそのままコミットしてpushする運用では、置換前の内容を土台に編集が進む点に気をつけます。差し替えが手元の refs/replace/ だけにある場合、他の人の環境や、置換が無いCIのチェックアウトでは、そもそも置換前の内容しか見えていません。
Claude Codeのworktree作成が置換を無視することは、この意味では「リモートや他の利用者と同じ内容から始める」挙動と見ることもできます。どちらが望ましいかは、置換エントリを何のために置いているかで決まります。
紛らわしい似た症状との切り分け
worktreeのトラブルシューティングには、「作ったworktreeの中身が想定と違う」類の項目がほかにもあります。この記事の症状と間違えやすいのは次の1つです。
- Git LFSのファイルがpointerのまま:
git lfs install --localでフィルターをリポジトリの.git/configに書いているとき。Claude Codeはリポジトリ固有のフィルタードライバーを実行しないため。回避はworktree内でgit lfs pull
置換の件は、中身が「別のファイルの内容」になるのに対し、LFSは「pointerファイルのテキスト」になります。見た目で区別できます。
また、同じ節にはworktree作成そのものが止まる原因として、symlinkと部分クローンが載っています。止まらずに作られて中身だけ違うなら、置換かLFSを疑います。詳しくはsymlinkで失敗する原因とpartial cloneでworktreeが作れないときの記事で扱っています。
よくある質問
GIT_NO_REPLACE_OBJECTS=0をexportすれば直りますか
直りません。ドキュメントには、利用者が export した値も無視されると書かれています。変数の設定では変えられないため、手動でworktreeを作る回避策を取ります。
手動で作ったworktreeでも、Claude Codeのworktree隔離は働きますか
ドキュメントの隔離チェックの節は、--worktree で始めた場合・EnterWorktree で入った場合・再開した場合に同じ規則が適用されると書いています。手動で作ったworktreeで起動した場合の挙動は、この節には記載がありません。メインのチェックアウトへの書き込みを止めたいかどうかで、claude --worktree を使う価値は変わります。
置換が効いているのはメインのチェックアウトだけですか
メインのチェックアウトは、置換エントリを入れる前に展開されたファイルを持ち続けます。手元の再現でも、git replace のあとでメイン側の app.txt は original のままで、git show HEAD:app.txt だけが replaced を返しました。置換を入れたあとにチェックアウトし直したメインや、通常のgitで作ったworktreeは置換後の内容になります。メイン側が original のままなのは、置換より前に展開されたファイルを持ち続けているためで、Claude Codeのworktreeと同じ内容に見えます。比べる相手は git show HEAD:<パス> です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。