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
- 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).
- For 5xx or 429, treat as transient — the error is fallback-eligible, so route through isFallbackEligible() and use the worker path.
- For a persistent 4xx, fix the request shape (e.g. ensure projectId is set and the session was started) before retrying.
- 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
- Always validate required fields (projectId, eventType) before the call to avoid avoidable 400s.
- Distinguish transient (5xx/429) from permanent (4xx) http errors; only fall back on transient.
- Rotate API keys proactively to avoid 401/403 storms.
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
- missing_api_key
- Failed to get processing status: ${res.status}
- Failed to trigger processing: ${res.status}
- Failed to search: ${searchResponse.status} ${searchResponse.
- Failed to fetch SDK sessions: ${sessionsResponse.status} ${s
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/e5c07bab02d04f14.
Report an issue: GitHub.