thedotmack/claude-mem · error · Error

waitForSlot aborted before queuing

Error message

waitForSlot aborted before queuing

What it means

waitForSlot accepts an optional AbortSignal and checks it before joining the waiting queue. If the signal is already aborted when the caller would otherwise have to wait (pool full), the call throws immediately rather than queueing a promise that would be rejected anyway. Fast-fails work that was cancelled before it even started waiting.

Solutions

  1. Check signal.aborted before attempting the spawn and skip cancelled work
  2. Create a fresh AbortController per spawn attempt instead of reusing one
  3. Order operations so the abort signal is only created when the work is actually starting

Example fix

// before
await waitForSlot(max, reusedSignal); // reusedSignal already aborted
// after
const controller = new AbortController();
if (!controller.signal.aborted) {
  await waitForSlot(max, controller.signal);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (signal?.aborted) return; // skip cancelled work before requesting a slot

Type guard

function isAborted(signal?: AbortSignal): boolean { return signal?.aborted === true; }

Try / catch

try { const slot = await waitForSlot(max, signal); }
catch (e) { if (e.message === 'waitForSlot aborted before queuing') return; /* cancelled, not an error */ throw e; }

Prevention

When it happens

Trigger: Calling waitForSlot with an AbortSignal that is already aborted while the pool is full (activeCount >= max), so the pre-queue abort check fires.

Common situations: Request cancellation (HTTP client disconnect) completed before slot acquisition; timeouts that abort the signal earlier in the pipeline; reusing an aborted controller for a second spawn attempt.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/5517dc743a79f858. Report an issue: GitHub.

Appendix: source

Thrown at src/supervisor/process-registry.ts:577

 * mid-response.
 */
export async function waitForSlot(
  maxConcurrent: number | (() => number),
  signal?: AbortSignal,
  sessionId?: number | string
): Promise<SlotReservation> {
  const getMax = typeof maxConcurrent === 'function' ? maxConcurrent : () => maxConcurrent;

  getProcessRegistry().pruneDeadEntries();
  const activeCount = getActiveSdkCount();
  if (activeCount >= TOTAL_PROCESS_HARD_CAP) {
    throw new Error(`Hard cap exceeded: ${activeCount} processes in registry (cap=${TOTAL_PROCESS_HARD_CAP}). Refusing to spawn more.`);
  }

  if (activeCount < getMax()) return takeSlotReservation();

  if (signal?.aborted) {
    throw new Error('waitForSlot aborted before queuing');
  }

  logger.info('PROCESS', `Pool limit reached (${activeCount}/${getMax()}), waiting for slot...`);

  return new Promise<SlotReservation>((resolve, reject) => {
    let recheckTimer: ReturnType<typeof setInterval> | null = null;
    let abortHandler: (() => void) | null = null;
    const record: SlotWaiterRecord = {
      sessionId,
      parkedSince: Date.now(),
      warnedParked: false,
      notify: () => {},
    };
    const cleanup = () => {
      if (recheckTimer) clearInterval(recheckTimer);
      if (abortHandler && signal) signal.removeEventListener('abort', abortHandler);
      const idx = slotWaiters.indexOf(record);
      if (idx >= 0) slotWaiters.splice(idx, 1);

View on GitHub (pinned to d8bc9755e7)