AI開発で肥大化したWindowsのDドライブを整理:Git worktree・Cargo target・検証資料を分けて退避した手順

Claude CodeなどのAIエージェントとRustで開発していると、Dドライブの空きがいつの間にか減っていく。2026年11月8日、私はその整理をClaude Codeに任せて実施した。実施の結果、Dドライブの空きは Cargo target の退避で約35.4GiB、旧Stage・検証資料などの退避で約34.4GiB、合わせて約69.8GiB増えた。

ただし、この記事の中心は容量の数字ではない。何を削除せず、何を移し、何をGitの手順で外したのかという分類と、コピーが本当に正しいかを確かめてから元データを消すという順序が主題だ。移動後にAIエージェントがファイルを見失った事例と、その調べ方も書く。

容量を食っているフォルダを見つける方法は、以前の記事「Windowsで容量を食っている大きなフォルダをローカルだけで調べる方法」で書いた。今回はその続きで、見つけた後に安全に整理・退避・復元する話を扱う。

何が容量を消費していたか

整理の対象は、AIエージェントとの開発で作業のたびに増えていった作業用フォルダだった。大きく3種類に分けられる。

  • Git worktree: ステージ(作業単位)ごとにブランチを分けて作業するために作ったもの。
  • Cargo target: Rustのビルド成果物。作業フォルダごとにtargetができていた。
  • 検証資料: 旧Stageの検証出力、スクリーンショット、ログ、ビルド用のログなど。

今回の実測値は次のとおりだ。数値は、保存していた台帳とログから集計し直したものである。

区分件数ファイル数論理サイズDドライブの空き増加
Cargo target の退避20件44,12938.76GiB約35.4GiB(ログ上 35.42GiB)
旧Stage・検証資料などの退避297件72,00834.87GiB34.38GiB(589.86→624.24GiB)
Git worktreeの解除8件記録なし記録なし実測していない

「論理サイズ」は台帳に記録したファイルのバイト数の合計で、「空き増加」は作業の前後で測ったDドライブの空き容量の差だ。この2つは別の指標なので、合計してはいけない。今回は両者が一致しなかった。たとえばCargo targetでは、論理サイズが空き増加より約3.3GiB大きい。原因は調べていない。Cargo の deps の実行ファイルがハードリンクになっている場合は差が出うるが、今回それが原因かどうかは確認していないので断定しない。また、空き容量の増加は、整理の最中に他のプロセスが書き込んだ分にも影響される。

重複計上も確認した。Cargo targetの退避20件のうち10件については、その親フォルダ(9件)が、後の297件の中にも現れる。ただし、親フォルダを処理した時点では target はすでに退避済みで、親フォルダの行は小さな残りだけを移している(9件が merge で、サイズはほぼ0)。同じバイトを二重に数えてはいない。

Git worktreeの8件については、解除したという私の認識があり、解除後は作業フォルダの wt ディレクトリが消え、git worktree list に載らなくなっている。ただし、解除の瞬間の台帳や、その作業で増えた容量の記録はない。そのため、worktreeの容量は上の2行に含めず、合計にも入れていない。

退避先は別ドライブ(Jドライブ)で、元のパスと同じ構成で置いた。たとえば D:\work\foo は J:\_tmp\D\work\foo に置く。この規則があるおかげで、あとで所在をたどれる。

削除・退避・正式解除の判断

整理では、対象を次のように分けて扱った。同じ「容量を食っているフォルダ」でも、処理の仕方がまったく違うからだ。

種類扱い確認すること
再生成できるビルド成果物(Cargo target)削除または退避他のビルドが共有していないか。target内に独自のファイルがないか
Git管理下のworktreegit worktree remove で正式に解除未コミットの変更がないか。ブランチにコミットが残っているか
再現に必要なfixture(入力データ)消さずに退避後続の検証が参照しているか
検証ログ・画像・レポート退避報告書から参照されているか
一時ファイル削除可他のプロセスが開いていないか
正本や未コミット変更を含む作業領域動かさないGitの状態、作業中のプロセス

「削除してよい」と判断する前には、次の点を確認する。

  • Gitのルート、登録済みworktree、他のプロセスのカレントディレクトリになっていないか。
  • ジャンクションやシンボリックリンクを含んでいないか。含む場合は、リンク先を追って消してしまう事故を避けるため、今回は追跡せずに保留にした。
  • 後続の作業が、その場所を参照していないか(環境変数 CARGO_TARGET_DIR で共有しているtargetなど)。

今回の整理には、未処理の35件、保留の7件がある。保留のうち5件は、ジャンクションやシンボリックリンクを含むために動かさなかった。残り2件は、後続の作業が共有しているtargetと、凍結している旧targetで、判断して保護した。

Git worktreeの安全な整理

まず、worktreeがいくつあるかと、どこにあるかを確認する。

git worktree list

出力には、ブランチやコミット、locked、prunable などの状態が付く。確認用の git worktree list --porcelain は、スクリプト向けに形式が安定している。

解除する前に、そのworktreeの状態を調べる。

git -C D:\work\stage-a\wt status --short --branch

--branch を付けているので、きれいな状態でも、先頭に ## ブランチ名 の行が1行表示される。その下に、変更を示す行(M など)や未追跡ファイルを示す行(??)が1行もなければ、未コミットの変更も未追跡ファイルもない。これを確かめてから、解除する。

git -C D:\work\main-repo worktree remove D:\work\stage-a\wt

解除は、worktreeの登録元になっているリポジトリ(ここでは D:\work\main-repo)から、解除するworktreeのパスを指定して実行する。ブランチは削除されない。

Git公式のドキュメントによると、git worktree remove で解除できるのは、変更も未追跡ファイルもない、きれいなworktreeだけだ。きれいでないものには --force が必要で、ロックされているものには --force が2回必要になる。メインのworktreeは解除できない。

私は、未コミットの変更があるworktreeを --force で消す手順は勧めない。そのworktreeに、ブランチにもまだ入っていない作業が残っている可能性があるからだ。まず、変更を別のブランチにコミットするか、どう扱うかを決めてから解除する。

今回の8件(Stage 90〜97)は、Jドライブへ移したのではない。きれいな状態を確認したうえで、Gitの正式な手順で解除した。解除したのはworktreeの登録と作業ツリーで、ブランチは残っている。実際、stage90-coordinate-audit から stage97-jww-visual-fidelity まで8本のブランチは、現在もリポジトリにある。一方、Stage 90〜97の作業用フォルダ自体(検証出力など)は残っていて、うち7件は、まだ退避していない。

注意したいのは、worktreeのフォルダを、エクスプローラーでそのまま別ドライブへ移さないことだ。Git公式は、リンクされたworktreeを普通のファイル操作で移すと、リポジトリとのつながりが壊れると説明している。移す場合は git worktree move、壊れた場合は git worktree repair を使う。フォルダを手で消してしまった場合の掃除には git worktree prune があり、--dry-run で何を消すかを先に確認できる。ただし、公式は、通常は git worktree remove を使うことを勧めている。

Windowsで git worktree remove が失敗し、他のプロセスがフォルダを掴んでいる疑いがあるときの調べ方は、別の記事「Windowsでgit worktreeを削除できないとき、ロック元プロセスを1コマンドで調べる」に書いた。

Cargo targetの扱い

Cargoは、ビルドの成果物と途中のキャッシュを target ディレクトリに置く。既定ではワークスペース直下の target で、CARGO_TARGET_DIR や build.target-dir、--target-dir で場所を変えられる。中には、debug/ や release/ ごとに、依存クレートのビルド結果(deps/)や増分コンパイルのキャッシュ(incremental/)が入る。作業フォルダごとにビルドすると、そのたびに同じ依存のビルド結果が複製されるので、容量が増えていく。

Cargo公式のドキュメントでは、最終的な成果物は利用者向けで、途中の成果物は内部用であり、構成は変わりうるとされている。つまり、中身に手を入れる前提のものではない。

退避と削除の違いは、次のとおりだ。

  • 削除: ソースから再ビルドできる。ただし、再ビルドには時間がかかる。cargo clean は、既定で target ディレクトリ全体を消す。--dry-run を付けると、消さずに対象だけを表示する。
  • 退避: 別の場所に残しておけるので、再ビルドを避けたい場合や、念のために戻せるようにしたい場合に向く。

今回の20件は、Jドライブへ退避した。再ビルドできる成果物だが、戻せる状態を残すため、削除ではなく退避を選んだ。

退避するときのリスクもある。

  • target に、ビルド成果物ではない独自のファイル(ログや、測定結果)を置いていると、それも一緒に動く。
  • 絶対パスを含むファイル(Cargoのdep-infoは、既定でパスが絶対パスになる)があるので、別の場所に戻すとビルドの判定に影響しうる。
  • 他の作業が CARGO_TARGET_DIR で共有しているtargetは、動かすと後続のビルドが壊れる。

今回の台帳には、復元用のコマンドも記録した。形式は次のとおりだ。

robocopy "J:\_tmp\D\work\foo\target" "D:\work\foo\target" /E /COPY:DAT /DCOPY:DAT

Microsoftの公式ドキュメントによると、robocopyの引数は、コピー元、コピー先、オプションの順だ。/E は空のディレクトリを含めて、サブディレクトリをコピーする。/COPY:DAT はファイルのデータ、属性、タイムスタンプを、/DCOPY:DAT はディレクトリのデータ、属性、タイムスタンプをコピーする。コピー先にすでにあるファイルを削除するオプション(/PURGE や /MIR)は付けていない。ただし、コピー先にすでにファイルがあると、混ざったり、上書きされたりしうるので、復元先は存在しないか、空のフォルダにしておく。

終了コードは、0〜7が失敗なしを意味し、8以上は、コピーに失敗したファイルが1つ以上あることを意味する。0は何もコピーされなかった場合、1はすべてコピーされた場合だ。整理のログでは、退避のコピーが exit=1 で終わっていた。復元でも、終了コードが8以上でないことを確かめる。

Cargo の deps にある実行ファイルは、ハードリンクになっている場合がある。公式ドキュメントにハードリンクの扱いは書かれておらず、復元後も同じ状態になる保証はない。復元後のビルドに影響しないとも断言できない。復元したら、cargo build を実行して、問題なくビルドできるか確認する。失敗したり、不自然な再ビルドが起きたりしたら、target を作り直して、再ビルドすればよい。今回の整理では、復元は実行していない。

安全な退避の手順

次の工程を、はっきり分けて進める。コピーが成功したことだけを根拠に、元データを消してはいけない。

  1. 対象の棚卸し: 件数、サイズ、最終更新日、Gitの状態、ジャンクションの有無を調べる。
  2. 移動先の容量・競合確認: 空きが足りるか、同じ名前がすでにないかを確かめる。
  3. コピー: 元は消さずにコピーだけをする。
  4. ファイル数・サイズ・相対パスの照合
  5. SHA-256の照合
  6. 台帳への記録: 元のパス、退避先、件数、バイト数、照合結果、復元コマンドを残す。
  7. 元データ削除の判断: 上の照合がすべて合格し、直前にその場所が使われていないことを確認してから行う。
  8. 退避先からの復元確認(読者向けに勧める追加の検証): 実際に戻せるか、サンプルで確かめる。今回の整理では、復元は実行していない。

今回の整理では、元データを消すのは、コピー、全ファイルのSHA-256・相対パス・サイズ・ディレクトリ構成の一致、配置後の再確認、直前の使用状況の再確認が、すべて通った場合に限った。退避先がすでに存在するときは、上書きせず、名前が衝突しない場合だけ統合し、衝突するときは保留にした。

次のPowerShellは、コピー後の照合を、読み取りだけで行う読者向けの例である。ファイルの削除や移動はしない。実行するのは、コピーが終わったあとだ。相対パス、サイズ、SHA-256に加えて、空のディレクトリを含むフォルダ構成も比較する。ファイルを読み取れない、フォルダを列挙できない、ジャンクションやシンボリックリンクがある、といった場合は、成功とは扱わずに ERROR と表示して中断する。照合に合格したときだけ OK と表示し、結果は $true か $false で返す。

function Get-Inventory([string]$root) {
  $root = (Resolve-Path -LiteralPath $root).Path.TrimEnd('\')
  # 列挙エラー(アクセス拒否など)は例外にして中断する
  $all = @(Get-ChildItem -LiteralPath $root -Recurse -Force -ErrorAction Stop)
  $link = $all | Where-Object { $_.Attributes -band [IO.FileAttributes]::ReparsePoint } | Select-Object -First 1
  if ($link) { throw "reparse point found: $($link.FullName)" }
  foreach ($i in $all) {
    $rel = $i.FullName.Substring($root.Length + 1)
    if ($i.PSIsContainer) {
      [pscustomobject]@{ Rel = "$rel\"; Size = -1; Hash = '' }   # 空ディレクトリも比較対象にする
    } else {
      $h = (Get-FileHash -LiteralPath $i.FullName -Algorithm SHA256 -ErrorAction Stop).Hash
      [pscustomobject]@{ Rel = $rel; Size = $i.Length; Hash = $h }
    }
  }
}

function Test-Copy([string]$src, [string]$dst) {
  try {
    $a = @(Get-Inventory $src)
    $b = @(Get-Inventory $dst)
  } catch {
    Write-Host "ERROR: $($_.Exception.Message)"
    return $false
  }
  $diff = @(Compare-Object $a $b -Property Rel, Size, Hash)
  Write-Host "entries: src=$($a.Count) dst=$($b.Count)"
  if ($diff.Count -gt 0) {
    $diff | Select-Object -First 20 | Format-Table -AutoSize | Out-String | Write-Host
    Write-Host 'MISMATCH'
    return $false
  }
  Write-Host 'OK: relative path / size / SHA-256 / directory layout all match'
  return $true
}

# 例: コピー元とコピー先の照合(どちらも読み取りのみ)
Test-Copy 'D:\work\old-project' 'J:\archive\D\work\old-project'

ジャンクションやシンボリックリンクが混ざっていないかは、コピーの前に調べておく。

Get-ChildItem -LiteralPath 'D:\work\old-project' -Recurse -Force -Attributes ReparsePoint -ErrorAction Stop |
  Select-Object FullName, LinkType, Target

エラーなく列挙でき、何も表示されなければ、リンクは含まれていない。アクセス拒否などで列挙に失敗したときは、リンクがないとは判断できないので、成功扱いにせず、原因を解消してからやり直す。

使ったコマンドの仕様はMicrosoftの公式ドキュメントで確認した。Get-FileHash の既定のアルゴリズムはSHA256で、-LiteralPath はパスを文字どおりに扱う。Copy-Item は、コピー元を消さないコマンドだ。-Recurse を付けても、コピー先がすでにあると、ネストの仕方によって意図しない場所に入ることがあるので、コピー先は新しいフォルダにしておくのが安全だ。実際に私が使ったのはrobocopyで、上の確認用のスクリプトは、この記事のために書き直した例である。

この例は、Windows PowerShell 5.1 上の小さなテスト用フォルダで、6通りを試した。完全一致だけが OK になり、ファイル内容の変更、ファイルの欠落、空ディレクトリの欠落は MISMATCH、ほかのプロセスがファイルを開いていて読めない場合とジャンクションを含む場合は ERROR になった。いずれも $false が返り、誤って OK と表示されたものはなかった。ただし、これは読者向けに書いた例で、実際の整理で使ったスクリプトとは別物だ。私が実際に使ったコマンドそのものではなく、大きなフォルダや、ネットワーク越しの照合では試していない。

AIエージェントによる紛失の誤認

整理のあと、別の作業でClaude CodeがDドライブの旧パスを見に行き、ファイルが見つからないという理由で、検証用の入力データ(fixture)を紛失したと判断した。実際には、そのfixtureは整理の中でJドライブへ退避してあった。

退避先は、J:\_tmp\D\ の下に、元のパスと同じ構成で置いてある。今回のfixtureなら、元は D:\work\stage85 にあり、退避先は J:\_tmp\D\work\stage85\ だ。台帳には、このフォルダが307ファイル、562,648,192バイトで、照合に合格し、元データを削除したことが記録されている。今回、記事のために退避先を調べ直したところ、307ファイル(約536.6MiB)があり、台帳と一致した。

Dドライブにファイルがないとき、削除されたとすぐに判断してはいけない。まず、退避先のドライブを確認する。AIエージェントに作業を引き継ぐときは、次のことを伝えておくと、こうした誤認を防げる。

  • 整理を行った日付と、退避先のルート(今回なら J:\_tmp\D\)。
  • 元のパスと同じ構成で置いてあること。
  • 台帳の場所と、復元コマンドの書いてある列。
  • Dドライブにファイルがないときは、まず退避先を調べること。

整理を担当したClaude Codeは、退避先のルートに、中断記録と再開手順を書いたREADMEを置いた。台帳、ログ、元のリスト、分類の理由を書いた一覧を一緒に保存してあるので、所在を後から追跡できる。

限界と注意事項

  • Jドライブへの退避は、独立したバックアップではない。 同じマシンにつながった別のドライブへ移しただけで、Jドライブが壊れれば戻せない。大事なデータには、別の場所のバックアップが要る。
  • 整理は途中だ。 未処理が35件(約124.9GiB)、保留が7件、コピーの途中で止まった一時フォルダが3件ある。これらの元データは、削除していない。
  • Git worktreeの解除と、フォルダの移動は別物だ。 解除はGitの管理から外す操作で、フォルダの中身をどうするかは別に判断する。
  • 全ファイルのハッシュ一覧は残していない。 照合はその場で行って合否を台帳に残したが、Cargo targetも含め、全ファイルのハッシュ値の一覧は、保存していない。台帳に残したのは、件数、バイト数、照合の結果だ。
  • 同じ容量が確保できるとは限らない。 今回の数値は、私の環境のものだ。ビルドの頻度やworktreeの数によって、結果は大きく変わる。
  • 私が検証したのは、Windows上のこの1回の整理だ。 ほかの環境や、大量の小さなファイルを含むフォルダなど、条件が違う場合の動作は確かめていない。

まとめ

  • 容量を食う対象は、worktree、Cargo target、検証資料のように、性質で分ける。それぞれ、扱いが違う。
  • worktreeはGitの手順で解除する。未コミットの変更があるものを、強制的に消さない。
  • Cargo targetは再ビルドできるが、中に独自のファイルがないか、共有されていないかを確認する。
  • コピーしたら、件数、サイズ、相対パス、SHA-256を照合し、台帳に記録してから、元データの削除を判断する。
  • 移動後にファイルが見つからないときは、削除されたと決めつけず、退避先と台帳をたどる。

参考資料