thedotmack/claude-mem · error · Error
${timeoutMessage} (timed out after ${timeoutMs}ms)
Error message
${timeoutMessage} (timed out after ${timeoutMs}ms) What it means
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).
Source
Thrown at scripts/check-pending-queue.ts:57
status: string;
isProcessing: boolean;
queueDepth: number;
activeSessions: number;
}
async function fetchWithTimeout(
url: string,
init: RequestInit | undefined,
timeoutMessage: string,
timeoutMs: number = WORKER_FETCH_TIMEOUT_MS,
): Promise<Response> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
return await fetch(url, { ...init, signal: controller.signal });
} catch (err) {
if ((err as { name?: string })?.name === 'AbortError') {
throw new Error(`${timeoutMessage} (timed out after ${timeoutMs}ms)`);
}
throw err;
} finally {
clearTimeout(timer);
}
}
async function checkWorkerHealth(): Promise<boolean> {
try {
const res = await fetchWithTimeout(
`${WORKER_URL}/api/health`,
undefined,
'Health check did not respond',
);
return res.ok;
} catch {
return false;
}View on GitHub (pinned to d768ba3643)
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).
Example fix
// before — default 10s timeout fires on a slow worker
const res = await fetchWithTimeout(`${WORKER_URL}/api/processing-status`, undefined, 'Failed to get processing status');
// after — allow a longer budget for known-slow endpoints
const res = await fetchWithTimeout(
`${WORKER_URL}/api/processing-status`,
undefined,
'Failed to get processing status',
30_000, // explicit timeout for the slow endpoint
); Defensive patterns
Strategy: retry
Validate before calling
// Cheap liveness check before the real call to fail fast on a blackholing host:
async function workerQuick(host: string, port: string, ms = 2000): Promise<boolean> {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), ms);
try { return (await fetch(`http://${host}:${port}/api/health`, { signal: ctrl.signal })).ok; }
catch { return false; } finally { clearTimeout(t); }
} Type guard
function isAbortError(e: unknown): boolean {
return e instanceof Error && (e as { name?: string }).name === 'AbortError';
} Try / catch
// fetchWithTimeout already centralises this (scripts/check-pending-queue.ts:45-63): // catch AbortError -> rethrow as a typed timeout; rethrow everything else. // Callers should wrap in try/catch and decide whether to retry or report.
Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Failed to get processing status: ${res.status}
- Failed to trigger processing: ${res.status}
- Worker request timed out after ${WORKER_FETCH_TIMEOUT_MS}ms:
- Request timed out after ${timeoutMs}ms
- SSE stream returned HTTP ${response.status}
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/33a5db9235fd12aa.
Report an issue: GitHub.