{"record":{"id":"e51d63f56b3317a8","repo":"thedotmack/claude-mem","slug":"request-timed-out-after-timeoutms-ms","errorCode":null,"errorMessage":"Request timed out after ${timeoutMs}ms","messagePattern":"Request timed out after (.+?)ms","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/shared/worker-utils.ts","lineNumber":61,"sourceCode":"const HOOK_READINESS_TIMEOUT_MS = readTimeoutEnv(\n  'CLAUDE_MEM_HOOK_READINESS_TIMEOUT_MS',\n  getTimeout(HOOK_TIMEOUTS.HOOK_READINESS_WAIT),\n  { min: 0, max: 300000 }\n);\n\nconst API_REQUEST_TIMEOUT_BOUNDS = { min: 500, max: 300000 } as const;\n\nexport async function fetchWithTimeout(url: string, init: RequestInit = {}, timeoutMs: number): Promise<Response> {\n  try {\n    // AbortSignal.timeout (Node 18+) replaces the manual setTimeout/clearTimeout\n    // race. On expiry it aborts with a TimeoutError DOMException.\n    return await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });\n  } catch (err: unknown) {\n    // Preserve the historical timeout-error message (\"...timed out...\") that\n    // callers match on (hook-command.ts, server-beta-client.ts) — the\n    // DOMException text is runtime-dependent, so normalize it here.\n    if (err instanceof DOMException && err.name === 'TimeoutError') {\n      throw new Error(`Request timed out after ${timeoutMs}ms`);\n    }\n    throw err;\n  }\n}\n\nlet cachedPort: number | null = null;\nlet cachedHost: string | null = null;\nlet cachedSettings: SettingsDefaults | null = null;\nlet cachedApiRequestTimeoutMs: number | null = null;\n\nfunction getWorkerSettingsPath(): string {\n  return path.join(SettingsDefaultsManager.get('CLAUDE_MEM_DATA_DIR'), 'settings.json');\n}\n\nfunction getWorkerSettings(): SettingsDefaults {\n  if (cachedSettings !== null) {\n    return cachedSettings;\n  }","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/shared/worker-utils.ts#L43-L79","documentation":"Thrown by fetchWithTimeout when AbortSignal.timeout fires before the fetch completes. The DOMException TimeoutError is normalized to a plain Error with the historical 'Request timed out after Nms' message that callers (hook-command.ts, server-beta-client.ts) string-match on. Non-timeout fetch errors are rethrown unchanged.","triggerScenarios":"A fetch(url, init) within fetchWithTimeout exceeds the passed timeoutMs; AbortSignal.timeout aborts with a TimeoutError DOMException, caught and rewrapped.","commonSituations":"Worker server slow or unreachable; oversized response body; network latency; the API_REQUEST_TIMEOUT_MS configured too low for the operation; the worker process hung/dead.","solutions":["Confirm the target worker/server is running and responsive (health check).","Raise the configured timeout (within bounds 500–300000ms) if the operation legitimately needs more time.","For transient slowness, retry with backoff; treat this as a recoverable network error.","If the worker is wedged, restart it and re-issue the request."],"exampleFix":"// before\nconst res = await fetchWithTimeout(url, {}, 1000);\n\n// after\nconst res = await fetchWithTimeout(url, {}, 10_000);\n// or retry on timeout\nfor (let attempt = 0; attempt < 3; attempt++) {\n  try { return await fetchWithTimeout(url, {}, 10_000); }\n  catch (e) { if (!/timed out/.test(e.message) || attempt === 2) throw e; }\n}","handlingStrategy":"retry","validationCode":"// Validate the worker is reachable before the timed request\nasync function workerHealthy(url: string): Promise<boolean> {\n  try { const r = await fetch(`${url}/health`, { signal: AbortSignal.timeout(2000) }); return r.ok; }\n  catch { return false; }\n}\nif (!await workerHealthy(url)) throw new Error('Worker unreachable before request');","typeGuard":null,"tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    return await fetchWithTimeout(url, init, timeoutMs);\n  } catch (err) {\n    const isTimeout = err instanceof Error && /timed out/.test(err.message);\n    if (!isTimeout || attempt === 2) throw err;\n    await new Promise(r => setTimeout(r, 250 * 2 ** attempt));\n  }\n}","preventionTips":["Size timeoutMs to the operation (within 500–300000ms bounds).","Health-check the worker before issuing long requests.","Treat 'Request timed out' as retryable with backoff for transient slowness."],"tags":["network","timeout","fetch","worker","retryable"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}