Natten før det her tog det samme arbejde hele min Hetzner-maskine ned. Tavst — ingen alarmer, ingen SSH, ingen ping — indtil en hardware-reboot fra Hetzners panel hentede den tilbage næste morgen. Hvordan en uncapped maskine dør stille af swap-thrashing er en historie for sig selv. Det her handler om den lille, app-niveau årsag, jeg fandt, da jeg gravede.
Jeg havde sat pks brain extract til at køre igennem ~6.700 sessioner. Den lavede et AI-resumé per session ved at spawne en Claude-arbejder ad gangen. Mens den kørte, holdt jeg øje med maskinen — og hukommelsen blev bare ved at vokse.
Den voksede. 30 GB i én proces-familie, 600+ processer, og den kom igen hver gang vi ryddede op. Den nemme forklaring var »extract'en lækker«. Den var forkert.
Læringen
Extract'en var velopdragen — Claude-arbejderne lå fladt på ~9 GB. Det, der voksede, var noget helt andet: hver spawnet arbejder loadede projektets .mcp.json ved opstart og startede en Aspire MCP-server, den aldrig skulle bruge. Den MCP-server fyrede en nuget search af, som hænger i vores devcontainer og bliver til en zombie. Én læk per arbejder. Tusindvis af arbejdere = tusindvis af hængende processer.
Pointen: en extract-arbejder læser JSON og skriver markdown. Den har brug for nul værktøjer. Men den arvede hele min MCP-stak, fordi det var defaulten — og defaulten er sat til min interaktive dev-session, ikke til en batch-arbejder, der kører tusind gange.
Fiksen var én linje på den måde, vi spawner Claude på:
claude --print --strict-mcp-config # ignorér .mcp.json helt — load nul MCP-servere
Med --strict-mcp-config ignorerer arbejderen .mcp.json fuldstændigt. Min interaktive Aspire MCP blev liggende i config'en til mit eget arbejde — kun batch-arbejderne springer den over. Beviset kom med det samme:
✓ leak-free · 15.8GB used · claude 32/9.6GB · aspire 0 · aspire-managed 0
32 arbejdere i gang ved fladt ~9.6 GB, og nul Aspire-processer. Resten af kørslen — 41 minutter — holdt fladt.
Hvad jeg tog med
Hver MCP-server koster hukommelse og opstart per Claude. På én interaktiv session mærker du det aldrig. Men i det øjeblik du spawner Claude programmatisk — i en løkke, i en batch, en arbejder per element — multipliceres den omkostning med antallet af arbejdere, og en MCP-server, der bare er lidt skæv i netop dét miljø, bliver til en læk, der skalerer med din load.
Så: når du spawner en Claude til én smal opgave, så giv den kun det, opgaven faktisk skal bruge. For en arbejder, der kun læser og skriver filer, er det rigtige antal MCP-servere nul. Spawn smalt.
(Og så er der det andet lag: selv en velopdragen app burde ikke kunne tage en hel maskine ned. Det burde host'en forhindre — med hukommelsesgrænser per container. Det er host-niveau-historien fra den nat.)
Vis hele fejlfindingen
Sådan så det ud at finde roden
Den første mistanke var altid forkert: at det var extract'en selv. At skille »arbejderen« fra »det, arbejderen starter« var hele øvelsen.
Først: hvad spiser hukommelsen egentlig? Ikke Claude.
Found it — and it's not the extract. The extract is well-behaved (claude ~8 GB flat, pks parent 0.16 GB). The memory is dominated by something else:
aspire-managed— 454 processes, ~30 GB RSS ← this is the real consumer and likely what's growing. 454 processes under one name is abnormal — smells like orphaned/leaked processes.
(verbatim fra sessionen)
Så: at dræbe dem gjorde det værre. Harness'en genstarter MCP-serveren, hver gang den dør:
The Claude Code harness auto-restarts the MCP server every time I kill it, and each restart fires a fresh hung
nuget search. So killing is whack-a-mole — it actually makes it worse (kill → harness restarts → new hung search).
Så: den faktiske mekanik. Det var her det klikkede:
pks brain extractruns aclaude --printworker per session, and every worker loads.mcp.jsonon startup → launches theaspireMCP server (aspire agent mcp) → which fires a hungnuget search. So the leak scales with extract workers — thousands of extracts = thousands of orphaned nuget procs.
Den første fiks var en stopgap — at slette aspire fra den delte .mcp.json. Den virkede, men ramte alle sessioner, også min interaktive dev-session, der gerne vil have Aspire MCP. Den rigtige fiks er per-spawn:
The proper fix is confirmed feasible — add
--strict-mcp-configto the extract's claude spawn so workers ignore.mcp.jsonentirely (no MCP servers at all, which is exactly right — extract workers only read JSON and write markdown, they need zero tools).
Linjen lever i external/pks-cli/.../ClaudeRunner.cs. Bagefter satte vi en monitor til at skrige ⚠ FIX FAILED i det øjeblik, der dukkede en Aspire-proces op under kørslen — og den blev tavs resten af de 41 minutter (én falsk alarm: min egen VS Code-terminal, der lovligt loadede Aspire MCP). Det er forskellen på at tro en fiks holder og at bevise det.
