CLAUDE LABEN
DIR950 — Claude のコネクタディレクトリに載る MCP サーバーが950本を超えました。発見される経路は整い、説明文と権限の粒度で差がつく段階ですRECSKILL — 7月21日から Record a Skill が使えます。画面を録画しながら手順を声に出すだけで、Claude が再実行できるスキルにまとめます(Pro・Max・Team)COWORK — 7月7日、Cowork が claude.ai の web と iOS・Android のベータへ広がりました。リモートセッションはサーバー側で走るため、端末を閉じてもスケジュール実行が続きますDEFOPUS — Claude Code の既定の Opus モデルが Opus 5 になりました。設定未変更の環境でも参照先が変わるため、コストを固定したい場合はモデル指定を明示してくださいLEAKFIX — 切り詰めた MCP ツール出力の全文がセッション終了までメモリに残るリークが修正されました。Windows の自動更新失敗も併せて直っていますCTRL — 絵文字ショートコードの補完とトランスクリプト書き込み警告の明確化に加え、サブエージェント・予算・バックグラウンドセッションの制御が締められましたDIR950 — Claude のコネクタディレクトリに載る MCP サーバーが950本を超えました。発見される経路は整い、説明文と権限の粒度で差がつく段階ですRECSKILL — 7月21日から Record a Skill が使えます。画面を録画しながら手順を声に出すだけで、Claude が再実行できるスキルにまとめます(Pro・Max・Team)COWORK — 7月7日、Cowork が claude.ai の web と iOS・Android のベータへ広がりました。リモートセッションはサーバー側で走るため、端末を閉じてもスケジュール実行が続きますDEFOPUS — Claude Code の既定の Opus モデルが Opus 5 になりました。設定未変更の環境でも参照先が変わるため、コストを固定したい場合はモデル指定を明示してくださいLEAKFIX — 切り詰めた MCP ツール出力の全文がセッション終了までメモリに残るリークが修正されました。Windows の自動更新失敗も併せて直っていますCTRL — 絵文字ショートコードの補完とトランスクリプト書き込み警告の明確化に加え、サブエージェント・予算・バックグラウンドセッションの制御が締められました
記事一覧/API & SDK
API & SDK/2026-07-31上級

400文字だけ残したはずのツール出力が、1MB を握り続けていた

自作の MCP サーバーやツールラッパーで出力を切り詰めても、Node の sliced string は元の全文を握り続けます。境界が13文字であること、平坦化が効く書き方と効かない書き方、ヒープスナップショットから親を数える監査スクリプトまで、すべて実測して記録しました。

MCP50Node.js4メモリ6パフォーマンス4運用設計18

プレミアム記事

自分のサイトのビルドログを読ませるだけの、ごく小さな MCP サーバーを書きました。ツールはひとつ、指定したログファイルを読んで返すだけです。返り値が大きくなりすぎないよう、先頭400文字だけをプレビューとして履歴に残し、全文は必要になったときに読み直す設計にしました。

個人開発でいくつものサイトを回していると、こうした小さな常駐プロセスが少しずつ増えていきます。 一つひとつは数十メガバイトのつもりでした。

数時間走らせたあと、ps の出力を見て手が止まりました。常駐プロセスの RSS が 200MB を超えています。

履歴に積んであるのは400文字のプレビューだけのはずです。何百件積んでも数十キロバイトにしかなりません。それでもメモリは減りませんでした。

原因にたどり着くまで、私はまったく違う場所を疑っていました。ストリームの読み残し、Buffer の解放漏れ、イベントリスナの積み上がり。そのどれでもありませんでした。

犯人は、切り詰めるために書いたたった一行の slice でした。

400文字のプレビューが、親の全文を握って離さない

まず最小の再現から始めます。4MB の文字列を20回作り、それぞれ先頭200文字だけを配列に残す。それだけのコードです。

// exp1.mjs — node --expose-gc exp1.mjs slice
function mb(n) { return (n / 1048576).toFixed(1); }
 
const KEEP = 200;
const N = 20;
const SIZE = 4 * 1024 * 1024;
const kept = [];
 
global.gc();
const base = process.memoryUsage().heapUsed;
 
for (let i = 0; i < N; i++) {
  const full = 'x'.repeat(SIZE) + '#' + i;  // 4MB のツール出力を模擬
  kept.push(full.slice(0, KEEP));            // 200文字だけ残すつもり
}
 
global.gc();
const after = process.memoryUsage().heapUsed;
 
console.log('kept =', kept.length,
            '/ previewLen =', kept[0].length,
            '/ heapDelta =', mb(after - base), 'MB');

global.gc() を明示的に呼んでいるのは、GC のタイミングに結果が左右されないようにするためです。--expose-gc フラグが必要になります。

実行結果です。計測環境は Node.js v22.22.3(V8 12.4.254.21)、Linux x86_64・4 vCPU・メモリ 3.9GB のサンドボックスです。

kept = 20 / previewLen = 200 / heapDelta = 80.0 MB

保持しているのは200文字の文字列が20本、合計4,000文字です。それなのに 80MB が回収されていません。4MB × 20 と、きれいに一致します。

つまり、切り詰めたはずの親文字列が20本とも生きています。

境界はちょうど13文字だった

ここで気になったのは、「では何文字までなら安全なのか」という点です。プレビュー長だけを変えて測り直しました。

// exp3.mjs — node --expose-gc exp3.mjs <KEEP>
const KEEP = Number(process.argv[2]);
const N = 10;
const SIZE = 2 * 1024 * 1024;
const kept = [];
 
global.gc();
const base = process.memoryUsage().heapUsed;
for (let i = 0; i < N; i++) {
  const full = 'x'.repeat(SIZE) + '#' + i;
  kept.push(full.slice(0, KEEP));
}
global.gc();
console.log('keep =', KEEP, '/ retained =', ((process.memoryUsage().heapUsed - base) / 1048576).toFixed(2), 'MB');

2MB × 10本 を作った場合の結果です。

プレビュー長(文字)回収されずに残った量解釈
12.00 MB親は解放されている
52.00 MB親は解放されている
112.00 MB親は解放されている
122.00 MBここまでは安全
1320.00 MBここから親を握る
1420.00 MB親を握る
20020.00 MB親を握る

12と13のあいだで、2MB から 20MB へ跳ねます。

残り続ける 2.00 MB は、ループの最後に作った full がまだ到達可能なままである分です。呼び出し回数を1回にしても40回にしても 2.00 MB のまま動かなかったので、こちらは定数のノイズだと判断しました。逆に13文字以上では、回数に正比例して積み上がります。

呼び出し回数残った量(プレビュー200文字・親4MB)
14.00 MB
28.00 MB
520.00 MB
1040.00 MB
2080.00 MB
40160.00 MB

きれいな直線です。長く走らせるほど確実に増えます。

この13という値は、V8 が部分文字列を「親への参照+開始位置+長さ」で表現する内部表現(sliced string)を使い始める最小長と一致します。短い切り出しはその場でコピーされるので親が残らない、というわけです。

短く切るほど安全、という感覚とは逆になります。プレビューを 12 文字に縮めれば回避できますが、12文字のプレビューには何の情報も載りません。実務では使えない回避策です。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
プレビュー長13文字を境に親文字列がまるごと生き残る挙動の実測(20回の呼び出しで 4MB → 80MB)
平坦化が効く5つの書き方と、効かない7つの書き方の比較表 — テンプレートリテラル・String()・repeat(1) はいずれも素通り
常駐ツールラッパーの200呼び出しで heapUsed 203.5MB → 4.6MB・RSS 245.0MB → 43.6MB に落ちた実測と、その代償の 16 ミリ秒
ヒープスナップショットから sliced string の親を集計し、予算超過で exit 1 を返す約50行の監査スクリプト(実行結果つき)
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

API & SDK2026-07-24
共通エージェント定義をセッション単位で差し替える運用設計
Managed Agents の agent_with_overrides を使い、1つの土台エージェントをセッションごとにモデル・プロンプト・ツール・MCP・スキルまで安全に差し替える設計パターンを、検証コードと実運用の落とし穴とともにまとめました。
Claude Code2026-07-16
夜間セッションの常駐メモリが朝には数GB — 蓄積の四つの出どころと、RSS を刻んで切り分ける
夜間に回した Claude Code のプロセスを朝に見ると、常駐メモリが数GBまで膨らんでいました。2.1.209 で塞がれた四つの蓄積源と、自分の構成に残る分を RSS の傾きで切り分ける方法、しきい値で畳む運用までを個人開発の目線で整理します。
API & SDK2026-07-26
Mcp-Session-Id を前提にしない MCP サーバーへ — 状態の置き場所を作り直す
MCP 仕様のリリース候補でセッションヘッダーが廃止されます。自作リモート MCP サーバーの結合を監査スクリプトで数え、署名付きカーソルへ移すまでの実測記録です。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →