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-vm | anon-rss |
|---|---|---|---|
| 6月18日 | バージョン2.1.181 | total-vm約42.2GB | anon-rss約30.9GB |
| 7月23日 | バージョン2.1.215 | total-vm約42.2GB | anon-rss約30.6GB |
| 8月1日 | バージョン2.1.220 | total-vm約21.3GB | anon-rss約14.9GB |
| 8月2日 | バージョン2.1.220 | total-vm約21.3GB | anon-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の報告者が実際に入れた値は、次のとおりです。
[wsl2]
memory=24GB
swap=16GB
[experimental]
autoMemoryReclaim=gradualmemoryはVMの上限で、暴走したclaudeがWindows側のメモリまで食い込むのを抑えます。swapは上限を超えそうなときの逃げ場です。autoMemoryReclaimは[experimental]セクションの項目なので、書く場所を間違えないようにします。24GBと16GBは報告者の環境(RAM 63.4GiB)に合わせた値で、そのままの転用は目安になりません。Windows側で常用するアプリのメモリを引いた残りをmemoryに回す、という考え方で決めます。
変更を反映するには、PowerShellでVMを止めてから起動し直します。
wsl --shutdownMicrosoftの説明によれば、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-reloadcontinueにすると、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点で見られます。
journalctl | grep 'Out of memory: Killed process'の出力にclaudeのバージョン文字列がプロセス名として出るanon-rssが数GB〜30GB台で、通常の数百MBを大きく超えている- 直後に
init.scope: Failed with result 'oom-kill'が続く 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上限を付けるのが取りかかりやすい順序です。