{"record":{"id":"4e055a87a742d560","repo":"thedotmack/claude-mem","slug":"hard-cap-exceeded-activecount-processes-in-reg","errorCode":null,"errorMessage":"Hard cap exceeded: ${activeCount} processes in registry (cap=${TOTAL_PROCESS_HARD_CAP}). Refusing to spawn more.","messagePattern":"Hard cap exceeded: (.+?) processes in registry \\(cap=(.+?)\\)\\. Refusing to spawn more\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/supervisor/process-registry.ts","lineNumber":571,"sourceCode":" * that is re-read on every recheck — pass a thunk so a mid-wait settings\n * change (raising CLAUDE_MEM_MAX_CONCURRENT_AGENTS) can release an\n * already-parked waiter without a worker restart (#2756). `sessionId`, when\n * provided, lets callers (SessionRoutes) detect via isSessionParkedForSlot()\n * whether this specific session is currently parked here, to distinguish a\n * provider-switch onto a parked generator from one that is already\n * mid-response.\n */\nexport async function waitForSlot(\n  maxConcurrent: number | (() => number),\n  signal?: AbortSignal,\n  sessionId?: number | string\n): Promise<SlotReservation> {\n  const getMax = typeof maxConcurrent === 'function' ? maxConcurrent : () => maxConcurrent;\n\n  getProcessRegistry().pruneDeadEntries();\n  const activeCount = getActiveSdkCount();\n  if (activeCount >= TOTAL_PROCESS_HARD_CAP) {\n    throw new Error(`Hard cap exceeded: ${activeCount} processes in registry (cap=${TOTAL_PROCESS_HARD_CAP}). Refusing to spawn more.`);\n  }\n\n  if (activeCount < getMax()) return takeSlotReservation();\n\n  if (signal?.aborted) {\n    throw new Error('waitForSlot aborted before queuing');\n  }\n\n  logger.info('PROCESS', `Pool limit reached (${activeCount}/${getMax()}), waiting for slot...`);\n\n  return new Promise<SlotReservation>((resolve, reject) => {\n    let recheckTimer: ReturnType<typeof setInterval> | null = null;\n    let abortHandler: (() => void) | null = null;\n    const record: SlotWaiterRecord = {\n      sessionId,\n      parkedSince: Date.now(),\n      warnedParked: false,\n      notify: () => {},","sourceCodeStart":553,"sourceCodeEnd":589,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/supervisor/process-registry.ts#L553-L589","documentation":"waitForSlot enforces a global hard cap (TOTAL_PROCESS_HARD_CAP) on active SDK subprocesses in the registry. After pruning dead entries, if the active count is at or above the cap, it throws instead of queueing a waiter — the system refuses to create more processes under any circumstance. This is a last-resort resource guard above the soft per-pool maxConcurrent limit.","triggerScenarios":"Requesting a slot reservation when the registry already holds TOTAL_PROCESS_HARD_CAP or more live SDK subprocess entries after pruning.","commonSituations":"Runaway spawn loops that never release slots; leaked subprocesses whose reservations were never released; load spikes beyond the configured cap; long queues combined with processes that hang and never exit.","solutions":["Reduce concurrent spawn pressure so active processes stay under the hard cap","Ensure every SlotReservation is released (including on error paths) to avoid slot leaks","Increase TOTAL_PROCESS_HARD_CAP only after confirming memory/CPU headroom","Investigate stuck subprocesses that never exit and keep registry entries active"],"exampleFix":"// before\nconst slot = await waitForSlot(maxConcurrent); // throws at cap\n// after\nif (getActiveSdkCount() >= TOTAL_PROCESS_HARD_CAP) {\n  await waitForActiveCountBelow(TOTAL_PROCESS_HARD_CAP);\n}\nconst slot = await waitForSlot(maxConcurrent);","handlingStrategy":"retry","validationCode":"const active = getActiveSdkCount();\nif (active >= TOTAL_PROCESS_HARD_CAP) await backoffUntil(() => getActiveSdkCount() < TOTAL_PROCESS_HARD_CAP);","typeGuard":null,"tryCatchPattern":"try { const slot = await waitForSlot(max); }\ncatch (e) {\n  if (e.message.startsWith('Hard cap exceeded')) { await sleep(backoff); return waitForSlotWithRetry(max); }\n  throw e;\n}","preventionTips":["Release every SlotReservation in a finally block","Cap concurrency at caller level below the hard cap","Monitor registry counts and alert before approaching the cap","Audit for subprocesses that hang and never get pruned"],"tags":["process","resource-limit","backpressure"],"backgroundTag":"value-out-of-range","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}