thedotmack/claude-mem · warning

Request timed out after ${timeoutMs}ms

Error message

Request timed out after ${timeoutMs}ms

What it means

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.

Source

Thrown at src/shared/worker-utils.ts:61

const HOOK_READINESS_TIMEOUT_MS = readTimeoutEnv(
  'CLAUDE_MEM_HOOK_READINESS_TIMEOUT_MS',
  getTimeout(HOOK_TIMEOUTS.HOOK_READINESS_WAIT),
  { min: 0, max: 300000 }
);

const API_REQUEST_TIMEOUT_BOUNDS = { min: 500, max: 300000 } as const;

export async function fetchWithTimeout(url: string, init: RequestInit = {}, timeoutMs: number): Promise<Response> {
  try {
    // AbortSignal.timeout (Node 18+) replaces the manual setTimeout/clearTimeout
    // race. On expiry it aborts with a TimeoutError DOMException.
    return await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
  } catch (err: unknown) {
    // Preserve the historical timeout-error message ("...timed out...") that
    // callers match on (hook-command.ts, server-beta-client.ts) — the
    // DOMException text is runtime-dependent, so normalize it here.
    if (err instanceof DOMException && err.name === 'TimeoutError') {
      throw new Error(`Request timed out after ${timeoutMs}ms`);
    }
    throw err;
  }
}

let cachedPort: number | null = null;
let cachedHost: string | null = null;
let cachedSettings: SettingsDefaults | null = null;
let cachedApiRequestTimeoutMs: number | null = null;

function getWorkerSettingsPath(): string {
  return path.join(SettingsDefaultsManager.get('CLAUDE_MEM_DATA_DIR'), 'settings.json');
}

function getWorkerSettings(): SettingsDefaults {
  if (cachedSettings !== null) {
    return cachedSettings;
  }

View on GitHub (pinned to d768ba3643)

Solutions

  1. Confirm the target worker/server is running and responsive (health check).
  2. Raise the configured timeout (within bounds 500–300000ms) if the operation legitimately needs more time.
  3. For transient slowness, retry with backoff; treat this as a recoverable network error.
  4. If the worker is wedged, restart it and re-issue the request.

Example fix

// before
const res = await fetchWithTimeout(url, {}, 1000);

// after
const res = await fetchWithTimeout(url, {}, 10_000);
// or retry on timeout
for (let attempt = 0; attempt < 3; attempt++) {
  try { return await fetchWithTimeout(url, {}, 10_000); }
  catch (e) { if (!/timed out/.test(e.message) || attempt === 2) throw e; }
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate the worker is reachable before the timed request
async function workerHealthy(url: string): Promise<boolean> {
  try { const r = await fetch(`${url}/health`, { signal: AbortSignal.timeout(2000) }); return r.ok; }
  catch { return false; }
}
if (!await workerHealthy(url)) throw new Error('Worker unreachable before request');

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await fetchWithTimeout(url, init, timeoutMs);
  } catch (err) {
    const isTimeout = err instanceof Error && /timed out/.test(err.message);
    if (!isTimeout || attempt === 2) throw err;
    await new Promise(r => setTimeout(r, 250 * 2 ** attempt));
  }
}

Prevention

When it happens

Trigger: A fetch(url, init) within fetchWithTimeout exceeds the passed timeoutMs; AbortSignal.timeout aborts with a TimeoutError DOMException, caught and rewrapped.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/e51d63f56b3317a8. Report an issue: GitHub.