Claude Media
Claude Code WSL2のメモリ暴走でVMが落ちるとき.wslconfigに書く設定

Claude Code WSL2のメモリ暴走でVMが落ちるとき.wslconfigに書く設定

WSL2でClaude Codeのheadless実行が11〜31GBまでメモリを使い、VMごと落ちる報告の中身と、.wslconfigのmemory・swap設定、cgroup上限での封じ込め方をまとめます。

WSL2でclaude -pのようなheadless実行を回していると、claudeプロセスが11〜31GBの匿名メモリを抱え、カーネルのOOM Killerが働いた末にWSL2のVMごと落ちる、という報告がGitHubのissue #83280に上がっています。.wslconfigのmemoryとswapでVMの上限を決めておけば、Windows側まで巻き込まれる事態は避けられます。ただし.wslconfigだけでは、VMの中で他のプロセスが道連れになる問題は止まりません。この記事は、報告された症状、.wslconfigに書く値、VMの内側で足す2つの封じ込め策を順に扱います。

issue #83280で報告されている症状

報告者の環境は、Windows 11(RAM 63.4GiB)上のWSL2です。VMの割り当ては既定の50%にあたる31GiBで、swapは8GiBでした。Ubuntu 24.04を使い、cronからheadlessでclaude --print "/<skill>" --dangerously-skip-permissionsを呼んでいます。セッションは長く、WebSearch・WebFetch・サブエージェント・大きなファイル書き込みを多用する作業でした。

journalctlで拾ったOOM Killの記録は、約6週間で6件あります。

日付バージョンtotal-vmanon-rss
6月18日バージョン2.1.181total-vm約42.2GBanon-rss約30.9GB
7月23日バージョン2.1.215total-vm約42.2GBanon-rss約30.6GB
8月1日バージョン2.1.220total-vm約21.3GBanon-rss約14.9GB
8月2日バージョン2.1.220total-vm約21.3GBanon-rss約11.4GB

6月18日の3件は同じプロセスに対する繰り返しの記録のため、表では1行にまとめています。正常な対話セッションは約490MBで、報告者は通常の数十倍にあたると見ています。

注目点はtotal-vmです。数週間離れた別々のセッションなのに、値が0.1%以内で揃っています。報告者は、作業量ではなくホストのRAM量から決まる確保サイズに見えると推測しています。同じ機材でも、2.1.181・2.1.215では約40GB、2.1.220では約20GBでした。これは報告者の仮説であり、原因として確認された事実ではありません。

WSL2ではClaude Codeだけでなく全部落ちる

報告者の解析では、被害がVM全体に広がる仕組みはWSL2側にあります。WSL2のプロセスはすべてinit.scopeに属し、systemdはこのスコープをOOMPolicy=stopで動かします。そのため、OOM Killerがclaudeを1つ殺しただけでも、systemdがinit.scope全体にSIGKILLを送ります。ログには次のように残っています。

systemd: init.scope: Killing process 660 (bash) with signal SIGKILL
systemd: init.scope: Failed with result 'oom-kill'.
systemd: init.scope: Consumed 28min 5.751s CPU time,
         29.8G memory peak, 7.9G memory swap peak.

28分のセッションで、RAM 31GiBとswap 8GiBを使い切った形です。報告者は本番のWebアプリと約24本のcronジョブが4回止まった、と書いています。自分のVMでこの設定を確かめるコマンドは次のとおりです。

systemctl show init.scope -p OOMPolicy

報告者の環境ではOOMPolicy=stopと返っています。手元でもstopなら、同じ連鎖が起こりうる構成です。

.wslconfigでVMの上限を決める

.wslconfigはWindows側の%UserProfile%\.wslconfigに置きます。既定では存在せず、自分で作るファイルです。Microsoftの説明では、VMに割り当てるmemoryの既定はWindowsの総メモリの50%、swapの既定は同じく総メモリの25%を切り上げた値です。autoMemoryReclaimの既定はdropCacheで、gradualにするとキャッシュを少しずつ回収します。

issueの報告者が実際に入れた値は、次のとおりです。

.wslconfig
[wsl2]
memory=24GB
swap=16GB
 
[experimental]
autoMemoryReclaim=gradual

memoryはVMの上限で、暴走したclaudeがWindows側のメモリまで食い込むのを抑えます。swapは上限を超えそうなときの逃げ場です。autoMemoryReclaimは[experimental]セクションの項目なので、書く場所を間違えないようにします。24GBと16GBは報告者の環境(RAM 63.4GiB)に合わせた値で、そのままの転用は目安になりません。Windows側で常用するアプリのメモリを引いた残りをmemoryに回す、という考え方で決めます。

変更を反映するには、PowerShellでVMを止めてから起動し直します。

wsl --shutdown

Microsoftの説明によれば、wsl --shutdownは動いている全ディストリビューションを止めます。作業中のセッションがないときに実行します。.wslconfigはWSL2を使うディストリビューションが対象で、Windows Build 19041以降が要件です。

ここで強調しておきたいのは、memoryを絞ることが暴走の抑止にならない点です。上限を下げると、OOMはむしろ早く起きます。.wslconfigの役目は、Windows側とホスト全体の被害を断つことです。VMの中の道連れは別の手当てが必要になります。

VMの内側で足す2つの封じ込め策

報告者は.wslconfigのほかに、VMの内側で2つの対策を入れています。いずれも根本修正ではなく、被害を小さくする位置づけだと本人が書いています。

1つ目は、claudeをメモリ上限つきの一時cgroupで起動する方法です。root権限は要りません。

systemd-run --user --scope --quiet --collect \
  -p MemoryMax=10G -p MemoryHigh=8G -p MemorySwapMax=2G \
  -- claude --print "/skill" --dangerously-skip-permissions

これでマシン全体のOOMが、claudeだけを対象にしたcgroup内のkillに変わります。cronのリトライ処理を持っていれば、そのまま再試行できます。数値の10G・8G・2Gは報告者の例で、自分のジョブの通常使用量に合わせて決めます。

2つ目は、init.scopeのOOMPolicyを書き換える方法です。

sudo mkdir -p /etc/systemd/system/init.scope.d
printf '[Scope]\nOOMPolicy=continue\n' | sudo tee /etc/systemd/system/init.scope.d/10-oom.conf
sudo systemctl daemon-reload

continueにすると、OOM Killerが1つのプロセスを殺しても他のプロセスを巻き込まなくなる、と報告者は書いています。システムのサービス定義に手を入れる操作なので、他のサービスへの影響をわかったうえで入れます。

このほか、issue内のコメントには、常駐させたユニットにMemoryHighとMemoryMaxのドロップインを足した例もあります。反映はsystemctl daemon-reloadだけで済み、再起動は不要でした。確認にはsystemctl showではなく、/sys/fs/cgroup/system.slice/<unit>/memory.maxの値を読むよう勧められています。

CLAUDE_CODE_TOOL_MEMORY_LIMITでは足りない理由

Claude Code自身にも、メモリ上限の環境変数があります。CLAUDE_CODE_TOOL_MEMORY_LIMITをLinuxとWSLで4Gのように設定すると、Bash・PowerShell・Monitorツールのコマンドが使えるメモリを制限できます。v2.1.233以降の機能です。使い方はCLAUDE_CODE_TOOL_MEMORY_LIMITの解説にあります。

ただし、ドキュメントが書く対象はツールのコマンドです。#83280で膨らむのはclaudeプロセス本体の匿名メモリで、ビルドや外部コマンドではありません。そのため、この変数は症状の主因には効かない設計に見えます。上のsystemd-runや.wslconfigを、本体を囲う手段として併用するのが筋です。この点を公式が明記しているわけではないので、手元でMemoryMaxが効いているかを確かめます。

自分の環境で当てはまるかを切り分ける

同じ症状かどうかは、次の4点で見られます。

  1. journalctl | grep 'Out of memory: Killed process'の出力にclaudeのバージョン文字列がプロセス名として出る
  2. anon-rssが数GB〜30GB台で、通常の数百MBを大きく超えている
  3. 直後にinit.scope: Failed with result 'oom-kill'が続く
  4. journalctl --list-bootsで、シャットダウン記録なしに起動が切り替わっている

測るときの注意が2つあります。issueのコメントによると、プロセスツリーが複数に分かれる構成では、ランチャーの1pidだけを見ると実際の使用量を過小に見積もります。報告では499MBに見えていたものが、ツリー全体では2,111MB(9プロセス)でした。cgroupのcgroup.procsを辿って合計します。

また、cgroupのmemory.currentにはページキャッシュが含まれます。報告者の環境ではmemory.currentが11GBでも、実際のanonは1.5GBでした。アラートはmemory.statのanon行に張ります。

「空きが十分あるのにlow memoryで止まる」場合は別の症状です。実際にメモリを使い切るこの件とは原因が異なり、WSL2のlow memory誤検知の解説で切り分けられます。メモリリーク全般の系譜はClaude Codeのメモリリークで120GB超・OOM Killになる原因と対処法が詳しく扱っています。

headless実行の側でできる備え

cronから回す側にも手はあります。まず1回の実行に壁時計のタイムアウトを付けます。報告者は試行ごとにタイムアウトを設けていましたが、それでも28分のセッションでRAMとswapを使い切っています。時間の上限だけでは足りないため、長大な作業は小さなセッションに分けるほうが安全側です。8月24日のコメントでは、v2.1.238のchangelogにある「サブエージェントのツール結果を表示範囲の外に出たら解放する」修正が、関連しうるものとして挙がっています。

claude -pの基本はClaude Code -pモードにまとめています。バックグラウンドで長時間コマンドを動かす場面はCtrl+Bのバックグラウンド実行が参考になります。

直っているのか、まだ分かっていない

issue #83280はopenのままで、bug・area:core・staleのラベルがついています。コメントは3件です。

8月4日のコメントでは、WSL2でもheadlessでもない環境(Proxmox/KVM上のUbuntu、対話セッション、RAM 31GiB)で、2.1.220が3回OOM Killされたと報告されています。total-vmはWSL2の報告と136kB以内で一致しました。8月7日には、RAM 8GBのUbuntu上でも、total_vmが各セッション同じ値(RAMの75%に当たる5.74GiB)に揃うという計測が出ています。

一方、8月24日の追記では、同じProxmox環境でv2.1.220から2.1.240まで上げたところ、53時間29分の稼働でRSSのピークが383MBに収まり、OOMも起きなくなりました。ただし投稿者自身が、これは別環境の1例で、WSL2・headlessの経路を確認した証拠ではないと断っています。2.1.238のchangelogに前述の解放修正がありますが、改善がそれによるかは切り分けられていません。

今のところ言えるのは、報告に書かれたバージョンの範囲でリークが再現し、その後の版で改善した環境も出ている、というところまでです。WSL2のheadless実行でv2.1.240以降に直ったかを示す報告は、issue内にありません。

まとめ

WSL2でheadlessのClaude Codeがメモリを食い尽くすと、Windows側は.wslconfigのmemoryとswapで守れます。VMの中の道連れは、systemd-runのcgroup上限とOOMPolicy=continueで減らせます。どれも暴走を止める手段ではなく、被害の範囲を狭める手段です。まずsystemctl show init.scope -p OOMPolicyを確かめ、stopであれば、cronで回すジョブからcgroup上限を付けるのが取りかかりやすい順序です。

この記事を共有:XはてブLinkedIn