Claude Codeを使っていると、メモリがじわじわ食われて、しまいにはマシンがスワップで這うように遅くなる。ひどいと固まる、カーネルパニックする、再起動を強いられる。こういう時、よく見る「Claude CodeがRAMを食う」系の対処(トークンやログの掃除)では直りません。あれはコンテキストの大きさの話で、ここで起きているのは別の事故です。
正体は、セッションが終わったのに生き残ったMCPサーバやサブエージェントの子プロセスが溜まって、RAMとswapを食い潰すことです。
これは実在の、繰り返し報告されている事故です。2026年5月から6月の起票だけでも、#64366(11個のMCPサーバが約300プロセスに増え、swap 24GB、4日で4回のカーネルパニック)、#68647(並列のサブエージェントが子プロセスを漏らし128GBを枯渇させmacOSのwatchdogがパニック)、#66020、#68933、#61748(150以上のプロセス・31GB)と、10件を超えます。同じ形が Copilot CLI や Codex でも報告されていて、これは一つの道具のバグでなく、エージェントの時代の構造的な事故です。
なぜ起きるのか
Claude Code(やサブエージェント)がMCPサーバを起動すると、そのサーバは子プロセスになります。セッションやサブエージェントが終わった時、本来はそのサーバも片付けられるはずです。ところが、片付けられずに残ることがある。残ったサーバはPID 1に再ペアレントされて、メモリを抱えたまま動き続けます。何日かセッションを回すうちに、死んだセッションのMCPサーバが何十個もメモリを取り合い、最後はOOM killer(かパニック)で終わります。
手順1 漏れているか見る(読むだけ・安全)
肝心なのは、「PID 1に再ペアレントされている」かつ「MCPサーバらしい見た目」の両方を満たすプロセスです。再ペアレントだけでは何の意味もありません。あなたの正当なデーモンもPID 1の子だからです。だから、PID 1のプロセスを見境なく殺してはいけません。
ps -eo pid,ppid,etimes,rss,args | awk '$2==1 && /@modelcontextprotocol|mcp-server-|[/ ]mcp[ _-]/ {printf "%s age=%ss %sMB %s\n",$1,$3,int($4/1024),$0}'
一行が一つの漏れたサーバで、PID・動いている秒数・実メモリ・コマンドが出ます。これが何個も出るなら、それがあなたのRAMです。
手順2 安全に回収する
次のスクリプトは、漏れを一覧にし、MCPの署名に一致するものだけを対象にします。既定はdry-run(何を消すか見せるだけで、何も消しません)。--killを付けた時だけ回収します。
#!/bin/bash
# mcp-orphan-reaper.sh — PID 1 に再ペアレントされた漏れMCPサーバを一覧(既定)/回収(--kill)
# MCP署名に一致しないプロセスには一切手を出さない
set -u
KILL=0; MIN_AGE=300
while [ $# -gt 0 ]; do case "$1" in
--kill) KILL=1 ;; --min-age) shift; MIN_AGE="${1:-300}" ;; esac; shift; done
SIG='@modelcontextprotocol|mcp-server-|modelcontextprotocol|[/ ]mcp([ _-]|$)|uvx[^|]*mcp|npx[^|]*mcp'
mapfile -t ROWS < <(ps -eo pid=,ppid=,etimes=,rss=,args= \
| awk -v sig="$SIG" -v m="$MIN_AGE" '$2==1 && $3>=m {
a=""; for(i=5;i<=NF;i++) a=a (i>5?" ":"") $i; if(a~sig) print $1"\t"$3"\t"$4"\t"a }')
[ "${#ROWS[@]}" -eq 0 ] && { echo "漏れたMCPサーバは見つかりません(PID 1 + MCP署名, ${MIN_AGE}秒以上)。"; exit 0; }
tot=0; echo "漏れたMCP/エージェントのサーバ:"; printf ' %-8s %-7s %-7s %s\n' PID 'AGE' 'MB' CMD
for r in "${ROWS[@]}"; do IFS=$'\t' read -r p a rss c <<<"$r"; tot=$((tot+rss))
printf ' %-8s %-7s %-7s %s\n' "$p" "$a" "$((rss/1024))" "$(echo "$c"|cut -c1-72)"; done
echo " --- ${#ROWS[@]} 個, 約 $((tot/1024)) MB 回収可。"
if [ "$KILL" -eq 1 ]; then for r in "${ROWS[@]}"; do kill -TERM "${r%%$'\t'*}" 2>/dev/null \
&& echo "killed ${r%%$'\t'*}"; done
else echo "dry-run。確認したら --kill で回収、または kill <PID> で個別に。"; fi
私はこれをWSL2の実機で確かめました。偽のMCP署名つきの孤児を検出し、MCP署名でない正当なPID 1のデーモンには手を出さず、きれいに回収します。安全のすべては署名の一致で、MCPサーバらしく見えないプロセスには絶対に信号を送りません。
手順3 そもそも上限を付けて、二度とパニックさせない
回収は後始末です。本当の対策は天井を付けることです。MCPを多く使う重いセッションを、メモリの上限の下で起動しておけば、暴走した漏れがあっても上限に当たって死ぬだけで、マシン全体を道連れにしません。
# systemd のホスト: 8GBでセッションのcgroupを殺す(スワップ死でなく)
systemd-run --scope -p MemoryMax=8G claude
# どのシェルでも: 仮想メモリの上限(KB)をセッションと子に課す
( ulimit -v 8000000; claude )
天井は、実際の作業量より上で、総RAMより十分下に取ります。これで漏れは、再起動でなく、ただの煩わしさで済みます。
次のセッションの前に気づく
一番安いのは、次のセッションの始まりに、もう漏れが溜まっているかを警告してもらうことです。これをする(警告だけで何も殺さない・署名で安全に絞る)SessionStartのhook mcp-orphan-leak-warner.sh を、無料の安全装置の集まり cc-safe-setup に入れました。上のスクリプトは導入なしでそのまま使えます。
- 無料の安全装置(このhookも収録): https://github.com/yurukusa/cc-safe-setup
「Claude CodeがRAMを食う」と思ったら、まずpsで、PID 1のMCPサーバが溜まっていないかを見る。トークンの掃除の前に、そこを見てください。