{"record":{"id":"769c7ed13b6402b4","repo":"thedotmack/claude-mem","slug":"server-method-path-failed-message","errorCode":null,"errorMessage":"Server ${method} ${path} failed: ${message}","messagePattern":"Server (.+?) (.+?) failed: (.+?)","errorType":"exception","errorClass":"ServerClientError","httpStatus":null,"severity":"error","filePath":"src/services/hooks/server-client.ts","lineNumber":373,"sourceCode":"    const url = `${this.baseUrl}${path}`;\n    const init: RequestInit = {\n      method,\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: `Bearer ${this.apiKey}`,\n      },\n    };\n    if (body !== undefined) {\n      init.body = JSON.stringify(body);\n    }\n\n    let response: Response;\n    try {\n      response = await fetchWithTimeout(url, init, this.timeoutMs);\n    } catch (error: unknown) {\n      const message = error instanceof Error ? error.message : String(error);\n      const isTimeout = /timed out|timeout/i.test(message);\n      throw new ServerClientError(\n        isTimeout ? 'timeout' : 'transport',\n        `Server ${method} ${path} failed: ${message}`,\n        { cause: error },\n      );\n    }\n\n    if (!response.ok) {\n      const text = await response.text().catch(() => '');\n      throw new ServerClientError(\n        'http_error',\n        `Server ${method} ${path} returned ${response.status}: ${truncate(text, 200)}`,\n        { status: response.status },\n      );\n    }\n\n    const text = await response.text();\n    if (!text || text.length === 0) {\n      // Endpoints we call always return JSON; a body-less success is unusual","sourceCodeStart":355,"sourceCodeEnd":391,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/hooks/server-client.ts#L355-L391","documentation":"Thrown by ServerClient.request when `fetchWithTimeout` itself throws — the request never got an HTTP response. The client inspects the underlying message: if it matches /timed out|timeout/i the kind is `timeout`, otherwise `transport` (DNS failure, ECONNREFUSED, TLS certificate error, socket reset). Both kinds are fallback-eligible, and `cause` carries the original error for diagnosis.","triggerScenarios":"The configured server base URL is unreachable: server process not running (ECONNREFUSED), wrong host/port in the URL, DNS name not resolving, self-signed/mismatched TLS cert, or the request exceeding `timeoutMs` (default from HOOK_TIMEOUTS.API_REQUEST). Also proxies/firewalls resetting the connection.","commonSituations":"Hooks configured for server mode while the claude-mem server is down or was never started; base URL pointing at localhost from a container where the server runs on another host; corporate MITM proxies breaking TLS; slow database making the server exceed the hook timeout.","solutions":["Verify the server is up and the base URL is right: `curl -i $BASE_URL/v1/health` (or any known route) from the same environment the hooks run in","For ECONNREFUSED, start the server process / fix host and port in the client configuration","For timeout kind, raise the client `timeoutMs` (or HOOK_TIMEOUTS) only after confirming the server is genuinely slow, and check server-side logs for the stall","For TLS failures, fix the certificate or point baseUrl at a URL whose cert validates; as a stopgap honor the fallback path rather than disabling verification","Because transport/timeout are fallback-eligible, catch ServerClientError and degrade to local operation instead of failing the hook"],"exampleFix":"// before\nawait client.recordEvent(input); // Server POST /v1/events failed: fetch failed\n\n// after\ntry {\n  await client.recordEvent(input);\n} catch (e) {\n  if (isServerClientError(e) && (e.kind === 'transport' || e.kind === 'timeout')) {\n    await fallbackToLocal(input); // fallback-eligible by design\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":"import { request } from 'undici';\nasync function serverReachable(baseUrl: string): Promise<boolean> {\n  try { await request(baseUrl, { method: 'HEAD', headersTimeout: 2000 }); return true; }\n  catch { return false; }\n}\nif (!(await serverReachable(baseUrl))) throw new Error(`claude-mem server unreachable at ${baseUrl}`);","typeGuard":"function isTransientServerError(e: unknown): boolean {\n  return isServerClientError(e) && (e.kind === 'transport' || e.kind === 'timeout');\n}","tryCatchPattern":"for (let attempt = 1; attempt <= 3; attempt++) {\n  try {\n    return await client.recordEvent(input);\n  } catch (e) {\n    if (!isServerClientError(e) || (e.kind !== 'transport' && e.kind !== 'timeout')) throw e;\n    if (attempt === 3) return fallbackToLocal(input); // fallback-eligible kinds\n    await sleep(250 * 2 ** (attempt - 1));\n  }\n}","preventionTips":["Health-check the server URL at startup so misconfiguration fails loudly before real work","Retry only transport/timeout kinds with backoff; http_error needs cause-specific handling","Keep server and hooks on the same host/network plan, and pin the base URL in config rather than composing it ad hoc"],"tags":["network","fetch","timeout","connection-refused","hooks"],"backgroundTag":"network-request-failed","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}