thedotmack/claude-mem · error · ServerClientError

http_error

http_error

Error message

Server ${method} ${path} returned ${response.status}: ${truncate(text, 200)}

What it means

After a successful transport round-trip, if response.ok is false (any 4xx/5xx) the client reads the body text (truncated to 200 chars) and throws an http_error ServerClientError carrying the status code. Only 5xx and 429 are fallback-eligible; other 4xx are treated as real client bugs and surfaced so they can be observed rather than silently swallowed.

Source

Thrown at src/services/hooks/server-client.ts:382

      init.body = JSON.stringify(body);
    }

    let response: Response;
    try {
      response = await fetchWithTimeout(url, init, this.timeoutMs);
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : String(error);
      const isTimeout = /timed out|timeout/i.test(message);
      throw new ServerClientError(
        isTimeout ? 'timeout' : 'transport',
        `Server ${method} ${path} failed: ${message}`,
        { cause: error },
      );
    }

    if (!response.ok) {
      const text = await response.text().catch(() => '');
      throw new ServerClientError(
        'http_error',
        `Server ${method} ${path} returned ${response.status}: ${truncate(text, 200)}`,
        { status: response.status },
      );
    }

    const text = await response.text();
    if (!text || text.length === 0) {
      // Endpoints we call always return JSON; a body-less success is unusual
      // but not fatal — return undefined-shaped object.
      return {} as T;
    }
    try {
      return JSON.parse(text) as T;
    } catch (error: unknown) {
      const err = error instanceof Error ? error : new Error(String(error));
      throw new ServerClientError(
        'invalid_response',

View on GitHub (pinned to d768ba3643)

Solutions

  1. Read the status from the thrown ServerClientError: 401/403 means regenerate/re-scope the API key; 400 means inspect the request payload (projectId, required fields).
  2. For 5xx or 429, treat as transient — the error is fallback-eligible, so route through isFallbackEligible() and use the worker path.
  3. For a persistent 4xx, fix the request shape (e.g. ensure projectId is set and the session was started) before retrying.
  4. Check server logs for the matching request if the truncated body text is not enough to diagnose.

Example fix

// before
const res = await client.endSession({ sessionId });

// after — branch on status
catch (e) {
  if (e instanceof ServerClientError && e.kind === 'http_error') {
    if (e.status === 404) { /* session already ended */ return; }
    if (e.isFallbackEligible()) { await worker.endSession({ sessionId }); return; }
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate request shape before sending to avoid 400-class http_error.
function assertRecordEvent(input: ServerRecordEventRequest): void {
  if (!input.projectId) throw new Error('projectId is required');
  if (!input.eventType) throw new Error('eventType is required');
  if (typeof input.occurredAtEpoch !== 'number') throw new Error('occurredAtEpoch must be a number');
}

Type guard

import { ServerClientError } from './server-client.js';

function isHttpError(e: unknown, status?: number): boolean {
  return e instanceof ServerClientError && e.kind === 'http_error' && (status === undefined || e.status === status);
}

Try / catch

try {
  return await client.startSession(input);
} catch (e) {
  if (e instanceof ServerClientError && e.kind === 'http_error') {
    if (e.status !== null && (e.status >= 500 || e.status === 429)) {
      return await worker.startSession(input); // transient -> fallback
    }
    // 4xx (non-429) is a real client bug — surface it
  }
  throw e;
}

Prevention

When it happens

Trigger: The server returns 401/403 (bad or expired API key / wrong project scope), 400 (malformed request body, missing projectId), 404 (unknown endpoint or sessionId), 409 (conflict), 429 (rate limited), or any 5xx (server fault). The thrown error's status field mirrors response.status.

Common situations: API key is valid-format but revoked or scoped to a different team/project (401/403); caller passed a projectId the key cannot access; recordEvent referenced a serverSessionId that does not exist; server hit an internal error or its database is down (500/503); rate limiter triggered during heavy hook bursts (429).

Related errors


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