Monorepo for Aesthetic.Computer aesthetic.computer
core reports neo-resource-audit.md
5.4 kB

Neo resource audit — 2026-06-11 #

Snapshot of what eats the 8 GB MacBook Neo's RAM/CPU during a normal working morning, what was changed in response, and what's left as operating practice. Companion to reports/js-tooling-audit.md.

Findings (mid-morning, 5 Claude sessions + netlify dev up) #

  • Swap was 5.2 GB used of 6 GB with only 28% memory free — the machine sits one heavy job away from thrashing at all times.
  • netlify dev's esbuild service (zip-it-and-ship-it) holds ~420–510 MB resident permanently and bursts to 80% CPU while functions rebuild. Biggest single resident process tied to dev work.
  • Claude sessions: ~690 MB across 6 processes, and each session spawns its own MCP stack — at audit time 60 MCP processes / 309 MB (6× emacs-mcp, 6× mail-mcp, ~16 chrome-devtools-mcp instances each with a watchdog sidecar). None orphaned — all parented to live sessions — it's pure per-session multiplication.
  • Chrome: 19 processes / ~450 MB (a single renderer hit 280 MB).
  • mu (mail indexer) spikes to ~380 MB / 30% CPU while indexing.
  • ffmpeg: 145 files under pop/ spawn it; only one passes -threads. Default = all 6 cores at normal priority, so a render competes head-on with the UI and every Claude session.

What changed #

  1. Slab menubar telemetry (slab/menubar-swift):
    • New monospaced row under Status: mem 28% free · swap 5.2/6G · load 4.2, orange when free < 15% or swap > 90% full.
    • Its submenu lists the top 6 processes by resident memory (MB / %CPU / name) + "Open Activity Monitor".
    • Each live /pop render row now shows the driver process's RSS (via proc_pid_rusage) next to its progress bar, so a running job is tied to what it costs. Children (ffmpeg/python) surface in the hogs list.
    • Sources: StateSnapshot.swift (SystemStats, readSystemStats(), residentMB(of:)), MenuBuilder.swift (buildSystem), rebuild with slab/menubar-swift/install.sh.
  2. QoS shims (toolchain/shims/ffmpeg, ffprobe): exec the real binary under taskpolicy -c utility. Renders use all idle cores at full speed but yield to the UI under contention. Wired ahead of homebrew in ~/.config/fish/config.fish on the Neo, so anything spawned from a shell (including node children resolving ffmpeg via PATH) inherits it. Scripts that hardcode /opt/homebrew/bin/ffmpeg bypass the shim.

Round 2 (same day): MCP de-duplication for parallel sessions #

Claude Code spawns every stdio MCP server once per session, so N parallel sessions pay N copies. Fixes, in order of leverage:

  1. mail-mcp + emacs-mcp → shared HTTP daemons. Both servers grew a --http [port] mode (stdio default untouched — codespaces keeps using .mcp.json as-is). On the Neo they run once under launchd (computer.aesthetic.mail-mcp :7765, computer.aesthetic.emacs-mcp :7766 — toolchain/mcp/install-daemons.sh), and local-scope http entries in ~/.claude.json shadow the stdio ones. 12 processes across 6 sessions → 2 total, shared by any number of sessions.
  2. chrome-devtools telemetry watchdogs killed. All three entries (local/chicken/panda) now pass --no-usage-statistics, which stops the ClearcutLogger from spawning a watchdog sidecar per instance (was ~16 extra processes).
  3. npx wrapper dropped. Entries point at a pinned global install (npm i -g chrome-devtools-mcp, fnm default bin) instead of npx -y chrome-devtools-mcp@latest — no registry check and no npx process tree per session. Update deliberately manual now: npm i -g chrome-devtools-mcp to bump.
  4. Local chrome-devtools is lazy. toolchain/mcp/minis-mcp.mjs is group-based: the local server enables on browser/chrome/devtools/screenshot/lighthouse/webpage/headless mentions (stamp ~/.claude/chrome-mcp-active), auto-disables after 12 h idle — same mechanism the chicken/panda pair already used. Sessions that never touch a browser no longer spawn it at all.

Existing sessions keep their old processes until restarted; the savings land as terminals close and reopen.

Deliberately not done #

  • Global NODE_OPTIONS=--max-old-space-size: on an 8 GB machine V8's default old-space cap is already ~2 GB, and the actual hogs (esbuild = Go, ffmpeg = C, Chrome) aren't V8 heap. A global cap changes nothing except adding a new crash mode for legit big renders.
  • Wrapping npm run site in taskpolicy: package.json scripts are shared with codespaces/linux; esbuild's CPU burn is bursty, not constant. Revisit only if it shows up pinned in the new hogs list.

Operating practice (the stuff config can't fix) #

  • Don't leave netlify dev running when not doing site work — it's ~600 MB (node + esbuild service) of permanent residency.
  • Each Claude session costs ~100–150 MB plus ~60 MB of MCP servers; five idle sessions ≈ 1 GB. Close finished terminals. If the chicken/panda DevTools MCPs keep appearing in sessions that never use them, shorten the minis-mcp hook's 12 h idle expiry.
  • Existing rules still apply: one Chromium, one ffmpeg, one whisper at a time; vitest --maxWorkers=1 (see agent memory "8 GB RAM concurrency limits").
  • Swap doesn't shrink once grown — after a heavy day the 5 GB swap file stays. A reboot is the only real reset.