{"record":{"id":"33a5db9235fd12aa","repo":"thedotmack/claude-mem","slug":"timeoutmessage-timed-out-after-timeoutms-ms","errorCode":null,"errorMessage":"${timeoutMessage} (timed out after ${timeoutMs}ms)","messagePattern":"(.+?) \\(timed out after (.+?)ms\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/check-pending-queue.ts","lineNumber":57,"sourceCode":"  status: string;\n  isProcessing: boolean;\n  queueDepth: number;\n  activeSessions: number;\n}\n\nasync function fetchWithTimeout(\n  url: string,\n  init: RequestInit | undefined,\n  timeoutMessage: string,\n  timeoutMs: number = WORKER_FETCH_TIMEOUT_MS,\n): Promise<Response> {\n  const controller = new AbortController();\n  const timer = setTimeout(() => controller.abort(), timeoutMs);\n  try {\n    return await fetch(url, { ...init, signal: controller.signal });\n  } catch (err) {\n    if ((err as { name?: string })?.name === 'AbortError') {\n      throw new Error(`${timeoutMessage} (timed out after ${timeoutMs}ms)`);\n    }\n    throw err;\n  } finally {\n    clearTimeout(timer);\n  }\n}\n\nasync function checkWorkerHealth(): Promise<boolean> {\n  try {\n    const res = await fetchWithTimeout(\n      `${WORKER_URL}/api/health`,\n      undefined,\n      'Health check did not respond',\n    );\n    return res.ok;\n  } catch {\n    return false;\n  }","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/scripts/check-pending-queue.ts#L39-L75","documentation":"fetchWithTimeout aborts the underlying fetch after WORKER_FETCH_TIMEOUT_MS (10s, scripts/check-pending-queue.ts:31) via an AbortController. When the abort fires, fetch rejects with an AbortError, which this helper rewraps into '<timeoutMessage> (timed out after <N>ms)'. The timeoutMessage is supplied per call site (health check, processing-status, processing POST).","triggerScenarios":"Any of the three worker endpoints (/api/health, /api/processing-status, /api/processing) taking longer than 10s to respond. Worker hung under heavy DB load, Chroma sync stalled, or network latency/blackhole to CLAUDE_MEM_WORKER_HOST:CLAUDE_MEM_WORKER_PORT.","commonSituations":"Worker processing a large backlog and not answering health/status promptly. Wrong WORKER_HOST/PORT env pointing at an address that blackholes (no RST, so the connect hangs to the timeout). Chroma embedding a huge batch and blocking the event loop.","solutions":["Confirm the worker is responsive: curl -m 2 http://$CLAUDE_MEM_WORKER_HOST:$CLAUDE_MEM_WORKER_PORT/api/health — if it also hangs, the worker is blocked, not the script.","Check worker logs for long-running tasks (Chroma sync, large observation batches) starving the HTTP loop; if so, reduce batch size or move embedding off the request thread.","Verify CLAUDE_MEM_WORKER_HOST/PORT resolve to the right host (a blackholing IP will hit the 10s timeout rather than refuse).","If legitimately slow endpoints are expected, raise WORKER_FETCH_TIMEOUT_MS in the script (conscious tradeoff — the default 10s is deliberate for interactive use)."],"exampleFix":"// before — default 10s timeout fires on a slow worker\nconst res = await fetchWithTimeout(`${WORKER_URL}/api/processing-status`, undefined, 'Failed to get processing status');\n\n// after — allow a longer budget for known-slow endpoints\nconst res = await fetchWithTimeout(\n  `${WORKER_URL}/api/processing-status`,\n  undefined,\n  'Failed to get processing status',\n  30_000, // explicit timeout for the slow endpoint\n);","handlingStrategy":"retry","validationCode":"// Cheap liveness check before the real call to fail fast on a blackholing host:\nasync function workerQuick(host: string, port: string, ms = 2000): Promise<boolean> {\n  const ctrl = new AbortController();\n  const t = setTimeout(() => ctrl.abort(), ms);\n  try { return (await fetch(`http://${host}:${port}/api/health`, { signal: ctrl.signal })).ok; }\n  catch { return false; } finally { clearTimeout(t); }\n}","typeGuard":"function isAbortError(e: unknown): boolean {\n  return e instanceof Error && (e as { name?: string }).name === 'AbortError';\n}","tryCatchPattern":"// fetchWithTimeout already centralises this (scripts/check-pending-queue.ts:45-63):\n// catch AbortError -> rethrow as a typed timeout; rethrow everything else.\n// Callers should wrap in try/catch and decide whether to retry or report.","preventionTips":["Keep WORKER_FETCH_TIMEOUT_MS (10s) aligned with how long endpoints realistically take; raise consciously for slow endpoints.","Verify CLAUDE_MEM_WORKER_HOST/PORT point at a responsive worker — a blackholing address wastes the full timeout.","Watch worker DB/Chroma load; a blocked event loop is the usual root cause of slow health/status answers."],"tags":["network","timeout","worker","abort","queue-script"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}