Claude Desktop(MSIX)で非ページプールが増え続けるときの原因と回避策
Windows版Claude Desktop(MSIX)でNtFCの非ページプールが増え続ける症状を、junction経由のオープン・fswatchプローブの再試行・git多重起動の3系統に分け、見分け方と回避策をまとめます。
Windows版のClaude Desktop(MSIXパッケージ)でコードタブを使っていると、カーネルの非ページプールが数時間から1日で数GBに膨らみ、マシンが重くなる、最悪の場合は再起動を繰り返す。そんな報告がGitHubに複数上がっています。ここで出てくるプールタグがNtFCで、ntfs.sysがファイルを開くときに確保する領域です。
原因は1つではありません。issueを読むと、トリガーは少なくとも3系統あります。AppData配下のjunctionをたどるオープン、エンジンが繰り返すfswatchプローブ、高頻度で起動されるgitです。見分け方と当面の回避策を、系統ごとに並べます。
非ページプールが増え続けるとはどういう状態か
非ページプールは、ページアウトされないカーネルのメモリ領域です。ここが埋まると、物理メモリに空きがあっても新しい割り当てが失敗し始めます。ユーザープロセスのメモリ使用量には現れないため、タスクマネージャーでどのアプリも大きくないのにメモリが足りない、という見え方になります。
報告されている規模は次のとおりです。
| 報告 | 規模 | 条件 |
|---|---|---|
| issue #96870 | 規模16GB機で9時間に2.7GB | 条件通常のエージェント作業 |
| #96870のコメント | 規模1営業日(約11時間)で26GB | 条件pytestのxdistワーカー24本、データ取り込み24プロセス |
| issue #95489 | 規模毎分約230MB、約4時間で27.8GB機の空きが0に | 条件セッションを開いて放置 |
| #95489のコメント | 規模起動約2時間で42.1GB。8日間で予期しない再起動が10回 | 条件コードタブのエンジンが各15万〜21.7万回/秒のI/O |
#95489のコメントでは、プールが逼迫するとバグチェックの原因ドライバーが毎回別の名前になり、GPUドライバーやNICを疑って1週間を費やした、と書かれています。ランダムなBSODに見えたら、ドライバーの前にNtFCを見る価値があります。
増えた分は、いずれのissueでも再起動まで戻りません。高速スタートアップが有効な環境ではシャットダウンでは解放されず、「再起動」が必要という指摘もあります。
3つのトリガーと見分け方
先に全体像を表にします。
| 系統 | 起点 | プールタグ | 参照するissue | 当面の回避 |
|---|---|---|---|---|
| A. junction経由のオープン | 起点セッションが起動する全プロセスが、%APPDATA%・%LOCALAPPDATA%配下のjunctionやシンボリックリンクを通る | プールタグNtFC | 参照するissue#96870 | 当面の回避リンクを通らないパスに置き換える |
| B. fswatchプローブの再試行 | 起点エンジンが一時フォルダーで失敗するプローブを繰り返す | プールタグNtFC | 参照するissue#95489 | 当面の回避CLAUDE_CODE_TMPDIRを%LOCALAPPDATA%の外へ |
| C. gitの高頻度起動 | 起点デスクトップアプリやエンジンがgitを短い間隔で何度も起動する | プールタグProc、Fileなど | 参照するissue#94478 | 当面の回避gitを起動させない使い方に寄せる |
3番目はNtFCではありません。「junctionやgitが原因」とひとまとめにされがちなので、タグで先に切り分けます。
プールタグを読む方法
issueの報告者たちは、poolmonの-b、PoolMonX、またはNtQuerySystemInformationのクラス22(SystemPoolTagInformation)でNtFCの行を読んでいます。どれも導入の手間がかかるので、まず粗い代替として、非ページプール全体のバイト数を見る方法が#95489に書かれています。
(Get-CimInstance Win32_PerfFormattedData_PerfOS_Memory).PoolNonpagedBytesこの値を20秒おきに読んで、Claude Desktopを開いたとたんに右肩上がりになるなら疑いが濃くなります。#95489の再現手順では、再起動直後はNtFCが0で、Desktopを起動してエンジンが立ち上がってから20秒ごとに70〜80MB増えました。エンジンのプロセスだけを止めるとその場で増加が止まり、セッションを開き直すと数秒で再開します。
系統A: junction配下を開くだけで確保が積み上がる
issue #96870は、系統Aの根本原因まで掘ったレポートです。MSIX版Claude Desktopでは、コードセッションが起動するすべてのプロセスが、%APPDATA%か%LOCALAPPDATA%にあるjunctionかシンボリックリンクを通って何かを開くたびに、NtFCの非ページプールを1ブロックずつ解放されないまま残します。bash、pwsh、git、node、python、フックのどれも対象です。
報告者のトレースでは、MSIXのAppData仮想化を担うbindflt.sysが、リンクを検出した場面で返ってきた再解析バッファを解放し忘れています。この経路はMicrosoftにFeedback Hubで報告済みです。根本はWindows側のバグとされ、Desktopがエージェントの子プロセスをすべてこの仮想化の内側で動かすことで表面化した、という整理です。
起点になりやすい場所
制限は%APPDATA%・%LOCALAPPDATA%配下のリンクです。普通に使っていると、次のツールがここにリンクを作ります。
| ツール | リンクの場所 | 報告された消費 |
|---|---|---|
| uv管理のPython | リンクの場所%APPDATA%\uv\python\cpython-3.12-windows-x86_64-none | 報告された消費python -c pass1回で65ブロック(22.9KB)。FlaskアプリでPythonを1回起動すると約1,250ブロック(440KB) |
| security-guidanceプラグインのフック | リンクの場所上のuvのjunction経由でpython3.12を起動 | 報告された消費フック1回で約600ブロック(200KB)。Bashツールで$(...)を含むコマンドを実行すると7回走り、約1.5MB |
| nvm-windowsなどのPATH項目 | リンクの場所.nodejs、Codexのbin | 報告された消費コマンド探索で届くたびに2×160B |
| pnpmのグローバル | リンクの場所%LOCALAPPDATA%\pnpm\global | 報告された消費sfw・socketの実行で約25ブロック |
数字は積み上がります。uv管理のPythonを使うプロジェクトでpytestを並列に回すと、起動するPythonの数だけ確保が走ります。#96870のコメントには、24ワーカーのxdistで5分間に152個のPythonが起動し、12万を超えるブロックが残った例があります。
失敗するオープンも、成功するstatも漏れる
コメントでは、条件が追加で絞り込まれています。
- 存在しないファイルを開く失敗でも漏れる。junction越しの失敗オープン5,000回で、5,003ブロックが残った報告がある
- 存在するファイルへの成功した属性取得は、報告者によって結果が割れている。あるビルドでは漏れず、別の報告者のビルドでは漏れた
- Python 3.13の
os.statはGetFileInformationByNameを使うため、標準ライブラリのインポートごとに漏れる。junction経由のos.stat20,000回で20,000ブロックが残った - npmのワークスペースが
node_modulesに作るjunctionも対象。AppData配下にインストールしたNodeアプリや、そこから起動されるMCPサーバーも同じ
ブロックの大きさはリンクの再解析データに比例します。uvのjunctionで208B、Temporary Internet Filesのjunctionで272Bです。
動作確認の再現スクリプトは何を見ているか
#96870には、約1分で試せる非昇格のPowerShellスクリプトが付いています。OSが全プロファイルに作る%LOCALAPPDATA%\Microsoft\Windows\Temporary Internet Files(INetCacheへのjunction)を2,000回開き、NtFCのカウンターの差分を取ります。比較用に、リンク先を直接開く対照も同じ回数だけ走ります。
Claude Desktopのセッション内では、1回のオープンで確保2・解放1になり、1ブロック(272B)が残りました。1回の実行で約544,000Bです。通常のPowerShellウィンドウから同じスクリプトを回すと、確保も解放も1回ずつで、残りは0でした。セッション内と外で結果が割れるなら、この系統に当てはまります。
回避策: リンクを通らない実パスに置き換える
#96870の報告者が、測定で0になったと書いている回避策は次の3つです。
- uv: 環境変数
UV_PYTHON_INSTALL_DIRを%USERPROFILE%\.uv\pythonのようなAppDataの外に向ける。Pythonを入れ直し、仮想環境とuvツールも作り直す。Python起動時のブロックは65から0に、フックは約600から0に減った - pnpm:
PNPM_HOMEをAppDataの外に向けて、グローバルパッケージを入れ直す。ただしpnpmのグローバル設定は%LOCALAPPDATA%\pnpm\configに残る - PATH: AppDataのjunctionをリンク先の実パスに置き換える。nvm-windowsの
.nodejsは.shimに変える。Codex CLIはCODEX_INSTALL_DIRをAppDataの外に向けないと、codex updateのたびにAppDataのbinが先頭に戻る
これらを適用すると、通常の使用でNtFCの残りは0に留まった、というのが報告者の結果です。
自分の環境にリンクがあるかは、次のような走査で探せます。深さを抑えているので、結果が多すぎる場合は-Depthを調整してください。
# AppData配下のリンク(junction・シンボリックリンク)を2階層まで探す例
Get-ChildItem $env:APPDATA, $env:LOCALAPPDATA -Recurse -Depth 2 `
-Attributes ReparsePoint -ErrorAction SilentlyContinue |
Select-Object FullName, LinkType, Target
# PATHのうち、リンクになっている項目を探す例
$env:PATH -split ';' |
Where-Object { $_ -and (Get-Item $_ -ErrorAction SilentlyContinue).LinkType }Desktopの外で動かせば漏れない
#96870では、Claude Desktopのパッケージコンテキストの外で起動したものは影響を受けない、とされています。コメントのA/B比較がこれを裏付けます。同じPythonを、Desktopのセッション内から起動すると20,000回のstatで20,000ブロック残り、WMIのWin32_Process.Createから起動すると2ブロックしか残りませんでした。
コメントでは、Windows Terminalのpwshタブから起動しても3ブロックで済んでいます。WTもMSIXパッケージですが、漏れはパッケージ一般ではなく、Claudeのパッケージの仮想化に従う、という整理です。エンジン自体にはパッケージIDが無いものの、仮想化の内側にあるために漏れるとされています。
重い処理を通常のターミナルで回し、Desktopのセッションは短い対話用に分ける、という使い方も一応の回避になります。Windows版の導入手順はClaude DesktopのWindows版インストールと初期設定、企業向けのMSIX配布はClaude DesktopをWindowsで一括導入する手順で扱っています。
参考: フックが発火源になる例
security-guidanceプラグインが絡む別の不具合は、AppXSvcのコミットメモリが主題です。症状がNtFCの非ページプールではないので、混同しないでください。そちらはsecurity-guidanceでAppXSvcのメモリが枯渇するWindowsの不具合と回避策にまとまっています。この記事の系統Aでは、同じプラグインのフックがuvのjunctionを経由するために、フック1回あたり約600ブロックを漏らす、という別の側面が出てきます。
系統B: fswatchプローブが一時フォルダーで再試行し続ける
issue #95489は、リンクを作っていない環境にも当てはまる可能性があるトリガーです。MSIX版Claude Desktopでコードタブのセッションを開くと、同梱のClaude Codeエンジンがfswatch-probe-<6文字>というファイルを%LOCALAPPDATA%\Temp\claude\で開こうとして失敗し、これを毎秒約3.8万回繰り返したと報告されています。失敗するたびにMSIXのAppDataリダイレクトを通り、NtFCが1ブロックずつ残ります。
どのバージョンで起きたか
報告者は、プローブの文字列が同梱エンジン2.1.271と2.1.275に含まれ、ターミナル版の2.1.268には無いことを確認しています。プローブが入ったのは2.1.268から2.1.271の間で、漏れに最初に気づいた日は、エンジン2.1.271がディスクに書かれた翌日だと書かれています。
エンジンのバイナリそのものは原因ではありません。同じclaude.exeをDesktopを終了したうえでWindows Terminalから起動すると、アイドル時のファイル操作は毎秒約2回で、NtFCは横ばいでした。Desktopを起動し直すと、毎秒約20万回に戻ります。
回避策: CLAUDE_CODE_TMPDIRをAppDataの外へ
報告者が検証した回避策は、ユーザー環境変数CLAUDE_CODE_TMPDIRを、%LOCALAPPDATA%の外のフォルダーに向けることです。Claude Desktopを完全に終了し、起動し直します。
# D:ドライブに専用フォルダーを作り、ユーザー環境変数として設定する例
New-Item -ItemType Directory -Path D:\claude-tmp -Force
[Environment]::SetEnvironmentVariable('CLAUDE_CODE_TMPDIR', 'D:\claude-tmp', 'User')
# その後、Claude Desktopを完全に終了してから再起動するこの変数は公式のenv-varsに載っています。上書き先の下に、Windowsでは/claude/が付く形でエンジンの一時ファイルが作られます。設定できる場所と効かない場所はCLAUDE_CODE_TMPDIRで内部一時ファイルの置き場所を変えるにまとめています。
設定後の結果は、エンジン2つを動かしてNtFCが0MB・生存ブロック0のまま、エンジンのファイル操作が毎秒11〜17回程度です。プローブの残骸ファイルも残っておらず、プローブが完了するようになったと読み取れます。
junctionは必須条件か
報告者の環境では、%LOCALAPPDATA%\Temp\claudeがDev Driveへのjunctionでした。junctionが無くても起きるかどうかは、このissueでは確定していません。ただしコメントの別の報告者も、同じパスがjunction(別ドライブ上の専用フォルダーへ)で、再現マシン3台すべてでjunctionがあったと書いています。一時フォルダーを別ドライブに逃がす設定を入れている環境は、まず疑う価値があります。
なお、欠けているLocalCache\Local\Temp\claudeフォルダーを手で作っても、漏れは止まりませんでした。フォルダーの有無ではなく、エンジンの一時パスが%LOCALAPPDATA%配下に置かれていること自体が引き金だった、というのが報告者の見立てです。
別のコメントでは、%TEMP%直下に新しく作ったjunctionの失敗オープンは0ブロックで、%APPDATA%・%LOCALAPPDATA%に作ったjunctionは1回あたり2ブロックでした。ビルドで差がある可能性があり、リンクの置き場所による違いは断定できません。
系統C: gitの高頻度起動は別の漏れを増幅する
issue #94478は、デスクトップアプリがgitを高頻度で起動し続ける報告です。計測では毎秒15〜20回で、1つにつきconhost.exeが付きます。1日あたり約200万プロセスです。
ここでの漏れはNtFCではありません。報告者のトレースでは、プロセスが終了してもプロセスオブジェクトがカーネルに残り続け、1プロセスあたり数KBの非ページプールを消費します。最初の報告者はWindows DefenderのWdFilter.sysが参照を戻し忘れていると書き、デスクトップアプリのポーリングが約25倍の増幅器になっていると述べています。1時間に300〜700MB、1日で約6GBです。
漏れる側はマシンごとに違う
増幅される側のドライバーは、環境によって別物です。
- 別の報告者は、DefenderのプラットフォームをインボックスのWdFilter(4.18.2201.11)に戻して、400プロセスを起動して比較しました。残ったプロセスオブジェクトは、9月の版で406個、インボックス版で414個と変わらず、Defenderの更新が原因ではないと結論づけています
- さらに別の報告者は、AMDのiGPUドライバー
amdkmdag.sysが、作成されたプロセスごとにファイルオブジェクトを2つ残すと、WPRのプールトレースで示しました。この環境ではプロセスオブジェクトは増えていません - 報告者の1人は、ASUSの充電通知ツールが毎分約110プロセスを起動していた例も挙げています。Claudeと無関係でも、プロセスを大量に作るものは同じ増幅をします
共通点は、プロセスの作成レートです。ドライバーの修正はそれぞれのベンダーの仕事ですが、gitの起動回数を減らせば、どのドライバーの場合でも消費が減ります。
gitの起動元は複数ある
コメントによると、gitの起動元はデスクトップアプリだけではありません。別の報告者が15msごとにプロセス一覧を取った結果は次のとおりです。
| 状況 | git起動回数 | 主な内容 |
|---|---|---|
| アイドル、セッション4つ、エージェントなし | git起動回数毎分13回 | 主な内容差分ペインのmerge-base、rev-list、ls-files、diff |
| サブエージェント約16個が稼働 | git起動回数毎分約1,000回 | 主な内容status --porcelain、remote get-url origin、rev-parse --abbrev-ref HEADなどの束 |
エンジンのエージェントごとに、5つのコマンドの束が走ります。束はタイマーで動くのではなく、ツールを呼び出すたびに走り、初期の報告では毎分5回ほどでした。1回の呼び出しがラッパーのgit.exe、本体のgit.exe、conhost.exeの3プロセスになるため、数が増えます。同じ報告者は、プロセスオブジェクトが1つ残るたびに非ページプールが約6〜7KB増えると計測しました。100エージェントのワークフローを2時間回すと、非ページプールが860MBから1,480MBに増え、別の日には2つのワークフローで5.7GBに達してマシンが固まったと書いています。
gitリポジトリの外で作業すると止まる
同じ報告者の追試では、エージェントの束のgit起動は、作業ディレクトリがgitリポジトリのときだけ発生しました。リポジトリではないフォルダーでOCRとリネームをサブエージェントに回すと、エンジン由来のgit起動は毎分0回でした。デスクトップアプリの差分ペインからの起動は、他のリポジトリのセッションが開いているかぎり、毎分16〜30回ほど残ります。
そのため、リポジトリを必要としない大量処理はリポジトリの外のフォルダーで走らせる、というのが報告者の挙げる当面の回避策です。
作業ツリーが大きいリポジトリでは、git statusの走査自体も重くなります(報告者の環境では1万ファイル超)。Desktopの差分ペインが見せる変更内容と、手元のgitツールの出力が食い違う件はClaude Desktopのgit maintenanceで、Sublime Mergeに存在しない差分が出るで扱っています。
3系統のうちどれに当たるかを判断する流れ
症状から絞り込む手順を、順番に並べます。
- 非ページプールが増えていることを確認する。
PoolNonpagedBytesを20秒おきに読む。Desktopを閉じて再起動した直後に低いなら、Desktopが起点 - Desktopを起動したまま、セッションを開かずに待つ。増えないなら、起点はセッション内の子プロセスで、系統AかB。次の手順で分ける
- セッションを開いて何もしない状態で、増加が毎分100MB級になるなら系統B。
CLAUDE_CODE_TMPDIRを設定してDesktopを完全に再起動し、増加が止まるか見る - 系統Bの対処で止まらず、
uv・pnpm・nvm・npmワークスペースなどを使っているなら系統A。AppData配下のリンクを実パスに置き換える - 増えているのがプロセス関連のタグ(
Procなど)で、gitの起動回数が多いなら系統C。リポジトリ外での作業に振り分けて、起動回数と増加量が下がるか見る
増えた分は、いずれの系統でも再起動で初めて戻ります。対処後の確認は、再起動後の値を基準に測り直します。
修正の見通しと、当面の手
Windows 11の25H2と26H2のビルドでも再現報告があります。#96870のコメントには、26H2のビルド10.0.26300.9550でも直っていないという報告があります。Microsoftには系統Aの根本原因がFeedback Hubで報告されていますが、回答や修正版はissue上に見当たりません。
Claude側の対処として、#96870は、Claude Codeセッションをパッケージの仮想化の外で起動する案を挙げています。FileSystemWriteVirtualizationを無効にする方法や、ブローカー経由で起動する方法です。#95489は、プローブの再試行回数に上限とバックオフを付けることと、Desktopがエンジンに渡す一時パスをリダイレクト対象外にすることを提案しています。どちらも提案の段階で、採用されたとは書かれていません。
当面の手は次の3つです。
- 環境変数で、一時パスとツールのインストール先をAppDataの外に出す
- 重いジョブを、Desktopのセッションの外か、リポジトリの外で回す
- 増えた分は再起動でしか戻らないので、対処のあとは再起動直後の値から測り直す
どれも公式の修正を待たずに試せます。