CLAUDE LABJP
DIR950 — Claude's connector directory now lists more than 950 MCP servers, so discovery is solved and the differentiator has shifted to clear descriptions and fine-grained permissionsRECSKILL — Record a Skill landed on July 21: screen-record yourself doing a task while narrating, and Claude turns it into a skill it can replay (Pro, Max, and Team)COWORK — On July 7 Cowork expanded to the web at claude.ai and to iOS and Android in beta, with remote sessions running server-side so scheduled work continues with every device closedDEFOPUS — Opus 5 is now the default Opus model in Claude Code, so untouched configs point somewhere new — pin the model explicitly if you need costs to stay predictableLEAKFIX — A leak that kept the full untruncated result of truncated MCP tool output in memory for the rest of the session is fixed, along with Windows auto-update failuresCTRL — Emoji shortcode autocomplete and clearer transcript write warnings arrive alongside tighter controls for subagents, budgets, and background sessionsDIR950 — Claude's connector directory now lists more than 950 MCP servers, so discovery is solved and the differentiator has shifted to clear descriptions and fine-grained permissionsRECSKILL — Record a Skill landed on July 21: screen-record yourself doing a task while narrating, and Claude turns it into a skill it can replay (Pro, Max, and Team)COWORK — On July 7 Cowork expanded to the web at claude.ai and to iOS and Android in beta, with remote sessions running server-side so scheduled work continues with every device closedDEFOPUS — Opus 5 is now the default Opus model in Claude Code, so untouched configs point somewhere new — pin the model explicitly if you need costs to stay predictableLEAKFIX — A leak that kept the full untruncated result of truncated MCP tool output in memory for the rest of the session is fixed, along with Windows auto-update failuresCTRL — Emoji shortcode autocomplete and clearer transcript write warnings arrive alongside tighter controls for subagents, budgets, and background sessions
Articles/API & SDK
API & SDK/2026-07-31Advanced

The 400-Character Preview That Was Still Holding On to a Megabyte

Truncating tool output in your own MCP server does not free the original. Node's sliced strings keep the parent alive. Here is the measured 13-character boundary, which flattening tricks actually work, and a heap-snapshot audit script that counts the retained parents for you.

MCP50Node.js4Memory5Performance4Operations14

Premium Article

I wrote a very small MCP server whose only job was to read my own build logs. One tool, one file read, one string returned. To keep the return value manageable I stored only the first 400 characters as a preview and left the full text to be re-read on demand.

As an indie developer running several sites at once, I accumulate little resident processes like this one at a steady drip. Each was supposed to cost me a few dozen megabytes.

A few hours into running it, ps stopped me cold. The resident set of that little process had crossed 200 MB.

The history array was supposed to hold nothing but 400-character previews. Hundreds of them still add up to tens of kilobytes. The memory did not come down.

I spent a long time looking in the wrong places: unconsumed streams, Buffer handles I had forgotten to release, event listeners piling up on a long-lived emitter. None of it.

The culprit was the single line of slice I had written to do the truncating.

A 400-character preview that will not let go of its parent

Start with the smallest reproduction I could build. Create a 4 MB string twenty times, keep only the first 200 characters of each.

// exp1.mjs — node --expose-gc exp1.mjs
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;  // stand-in for a big tool result
  kept.push(full.slice(0, KEEP));            // "keep only 200 characters"
}
 
global.gc();
const after = process.memoryUsage().heapUsed;
 
console.log('kept =', kept.length,
            '/ previewLen =', kept[0].length,
            '/ heapDelta =', mb(after - base), 'MB');

The explicit global.gc() calls are there so the result does not depend on when a collection happens to fire. They require the --expose-gc flag.

Every number below comes from Node.js v22.22.3 (V8 12.4.254.21) on Linux x86_64, 4 vCPU, 3.9 GB of RAM, inside a sandbox.

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

Twenty strings of 200 characters. Four thousand characters in total. Eighty megabytes unreclaimed — exactly 4 MB times 20.

All twenty parents are still alive.

The boundary sits at exactly 13 characters

The obvious follow-up question is how short a preview has to be before it stops pinning the parent. Same script, varying only KEEP, this time with 2 MB parents and ten iterations.

Preview length (chars)UnreclaimedReading
12.00 MBparent freed
52.00 MBparent freed
112.00 MBparent freed
122.00 MBstill safe
1320.00 MBparent pinned
1420.00 MBparent pinned
20020.00 MBparent pinned

Between 12 and 13, the number jumps by an order of magnitude.

The residual 2.00 MB is the last full still being reachable when the measurement runs. It stayed at 2.00 MB whether I looped once or forty times, so I treated it as a constant floor. Above the threshold, the growth is strictly linear.

CallsUnreclaimed (200-char preview, 4 MB parent)
14.00 MB
28.00 MB
520.00 MB
1040.00 MB
2080.00 MB
40160.00 MB

A perfectly straight line. The longer the process runs, the worse it gets.

That 13 lines up with the minimum length at which V8 switches to representing a substring as a reference to its parent plus an offset and a length — a sliced string. Anything shorter is simply copied on the spot, which is why the parent goes away.

Note how that inverts the usual intuition: shorter is safer, but only below a threshold too small to carry any useful information. Trimming your previews to 12 characters technically fixes the leak and destroys the feature.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
The measured 13-character threshold where a preview starts pinning the entire parent string alive (4 MB becomes 80 MB over 20 calls)
A side-by-side table of 5 flattening techniques that work and 7 that silently do not, including template literals, String(), and repeat(1)
A 200-call tool wrapper benchmark: heapUsed 203.5 MB to 4.6 MB, RSS 245.0 MB to 43.6 MB, for a total cost of 16 milliseconds
A 50-line audit script that walks a heap snapshot, sums the retained parents, and exits 1 when they blow a byte budget
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API & SDK2026-07-24
Swapping Agent Config Per Session From One Shared Definition
A design pattern for running one base Managed Agent and overriding its model, prompt, tools, MCP servers, and skills per session with agent_with_overrides — with a validated factory and the operational traps I hit along the way.
Claude Code2026-07-16
Your Overnight Session Wakes Up at 3GB — Four Places Memory Piles Up, and How to Tell Them Apart
The Claude Code process I left running overnight had grown to 3.4GB of resident memory by morning. Here are the four accumulation sources closed in 2.1.209, how to separate what's left in your own setup by sampling RSS slope, and a watchdog pattern that folds a session before it hurts.
API & SDK2026-07-26
Rebuilding a Remote MCP Server That Never Needed Mcp-Session-Id
The MCP spec release candidate drops the session header. Here is how I audited my own remote server for session coupling and moved it to signed cursors, with measurements.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →