.maestro/playbooks/2026-03-12-CM-Issues-PRs/2026-03-12-Issues-PRs-Triage/TRIAGE-02-Process-Resource-Stability.md
These are the highest-severity bugs in the project: runaway CPU, unbounded memory growth, and orphaned processes. Each one degrades the user's entire system, not just claude-mem. The root causes are well-understood from code analysis — HNSW index reconstruction on startup, unbounded pending_messages queue recovery, and saved_hook_context bloat in Claude Code session files.
Issues addressed: #1248, #1250, #1262, #1269, #1249
Prerequisite: Phase 01 must be complete (PR #1325 for zombie process fix should already be merged).
Fix chroma-mcp CPU runaway on Apple Silicon (#1248, #1250). The root cause is HNSW index reconstruction at startup when the Chroma collection has many documents. In src/services/sync/ChromaMcpManager.ts:
Already implemented.
CLAUDE_MEM_CHROMA_LAZY_INIT(default'true') andCLAUDE_MEM_CHROMA_STARTUP_DELAY_MS(default'5000') exist inSettingsDefaultsManager.ts. Theworker-service.tsinitializeBackground()(lines 438-457) skips eager backfill when lazy init is enabled (default), and uses the startup delay when lazy init is disabled.ChromaMcpManageralready lazy-connects on firstcallTool()use. Tests attests/shared/settings-defaults-manager.test.tscover these settings (35/35 pass).
buildCommandArgs() (around line 177-221) and the startup sequence--hnsw-batch-size argument to the uvx command args to limit HNSW index build parallelism. If chroma-mcp doesn't support this flag, the alternative is to add startup throttlingsrc/services/worker-service.ts initializeBackground() (line 377+), add a configurable delay before calling chromaBackfill() — use setting CLAUDE_MEM_CHROMA_STARTUP_DELAY_MS with default '5000'src/shared/SettingsDefaultsManager.ts defaults and interfaceCLAUDE_MEM_CHROMA_LAZY_INIT setting (default 'true') that defers Chroma initialization until first search request rather than eager startup Fix unbounded pending_messages queue growth (#1262). The PendingMessageStore in src/services/sqlite/PendingMessageStore.ts recovers ALL pending messages on startup, which can cause 100%+ CPU when hundreds of messages accumulated during crashes:
Implemented. Added
MAX_RECOVERY_BATCH_SIZE(50) andMAX_PENDING_AGE_MS(24h) constants.resetStaleProcessingMessages()now deletes messages older than 24h. NewpruneExcessPendingMessages()caps recovery to 50 most recent per session. Worker startup calls both. 8 new tests pass (13 total in file). Commit: dd29c043.
PendingMessageStore.ts, find the recovery/startup path where stale messages are reset to 'pending'MAX_RECOVERY_BATCH_SIZE constant (value: 50) — on startup, only recover the N most recent pending messages per session. Delete messages older than the batch limitMAX_PENDING_AGE_MS constant (value: 24 * 60 * 60 * 1000 — 24 hours) — delete pending messages older than this threshold during recovery instead of re-queuing themresetStaleProcessingMessages() (around line 160), add the age check: DELETE FROM pending_messages WHERE created_at_epoch < ? with Date.now() - MAX_PENDING_AGE_MSSessionQueueProcessor to ensure it handles the reduced queue gracefullyFix CPU 100% from saved_hook_context bloat (#1269). Claude Code saves hook context to session files, and large observations cause the session file to grow, slowing all subsequent hook invocations:
Implemented. Added
MAX_CONTEXT_SIZE_BYTES(50,000) constant insrc/cli/handlers/context.ts. WhenadditionalContextexceeds 50KB, it's truncated with[Context truncated — exceeded 50KB limit. Use mem-search for full history.]before returninghookSpecificOutput. The observation handler (observation.ts) confirmed not to returnhookSpecificOutput, so no changes needed there. 7 new tests intests/hooks/context-truncation.test.ts(all pass).
hookSpecificOutput, additionalContext, and saved_hook_context to understand what data is being savedsrc/cli/handlers/context.ts), find where additionalContext is constructed[Context truncated — exceeded 50KB limit. Use mem-search for full history.]hookSpecificOutput so the bloated data never reaches Claude Code's session filesrc/cli/handlers/observation.ts) contributes to saved context size — if it returns large hookSpecificOutput, apply the same truncationInvestigate bun SIGKILL in Claude Code sandbox (#1249). When Claude Code spawns node→bun as a grandchild process, the sandbox may SIGKILL the bun process:
Investigated and fixed. Root cause confirmed: spawn chain is
Claude Code → sh -c → node (bun-runner.js) → bun (worker-service.cjs), making bun a grandchild that gets SIGKILL'd by the sandbox. Fix: createdplugin/scripts/bun-exec-runner.shwhich finds bun and usesexecto replace the shell process, so bun becomes a direct child of Claude Code. Updated all 8 hook commands inplugin/hooks/hooks.jsonto usebun-exec-runner.shinstead ofnode bun-runner.js. On Linux, falls back tonode bun-runner.jsfor stdin pipe compatibility (#646).bun-runner.jsretained for backward compatibility. 25 distribution tests pass including 7 new tests for the fix. Full suite: 1142 pass, 0 fail.
src/cli/hook-command.ts to understand the spawn chainsrc/services/infrastructure/ProcessManager.ts resolveWorkerRuntimePath() for how bun is resolveddetached: true to the spawn optionsdetached: true is already used, check if the sandbox is killing based on process group. An alternative is to use setsid on Unix to create a new sessionRun tests and build to verify stability fixes:
Verified.
npm test: 1132 pass, 3 skip, 0 fail across 65 files.npm run build-and-sync: all artifacts built successfully (worker-service 1843KB, mcp-server 350KB, context-generator 71KB, React viewer). Worker health check confirmed:status: ok, v10.5.5, initialized, MCP ready. No CPU spike observed.
npm test — all tests must passnpm run build-and-synccurl http://localhost:37777/api/health that the worker starts without CPU spike