abhigyanpatwari/GitNexus · error · BackendError
${message}
Error message
${message} What it means
BackendError thrown by assertOk() for any non-OK HTTP response that doesn't match a more specific code path. The message is derived from the response body's `error` or `message` field (falling back to statusText); the code is computed from status: 404→'not_found', 429→'rate_limited', 401+unauthorized→'unauthorized', 403+origin_not_allowed→'origin_blocked', other 4xx→'client', 5xx→'server'. For 429, retryAfterMs is parsed from the Retry-After header (delta-seconds or HTTP-date).
Source
Thrown at gitnexus-web/src/services/backend-client.ts:560
// express-rate-limit emits it on 429 with seconds (integer) or HTTP-date.
// We accept both shapes; an unparseable header yields undefined retryAfterMs.
let retryAfterMs: number | undefined;
if (response.status === 429) {
const header = response.headers.get('retry-after');
if (header) {
const seconds = Number(header);
if (Number.isFinite(seconds) && seconds >= 0) {
retryAfterMs = seconds * 1000;
} else {
const dateMs = Date.parse(header);
if (Number.isFinite(dateMs)) {
retryAfterMs = Math.max(0, dateMs - Date.now());
}
}
}
}
throw new BackendError(message, response.status, code, retryAfterMs);
};
const repoParam = (repo?: string): string => (repo ? `repo=${encodeURIComponent(repo)}` : '');
// ── API Methods ────────────────────────────────────────────────────────────
/** Server info from /api/info. */
export interface ServerInfo {
version: string;
launchContext: 'npx' | 'global' | 'local';
nodeVersion: string;
}
/** Fetch server info (version, launch context). */
export const fetchServerInfo = async (): Promise<ServerInfo> => {
const response = await fetchWithTimeout(`${_backendUrl}/api/info`);
await assertOk(response);
return response.json() as Promise<ServerInfo>;View on GitHub (pinned to d540b00184)
Solutions
- Inspect error.status and error.code to determine the category (client/server/not_found/unauthorized/etc.)
- For code 'unauthorized', prompt the user for the deploy access token and retry
- For code 'origin_blocked', open the local UI on the same host as the backend
- For code 'rate_limited', honor error.retryAfterMs before retrying
- For 5xx 'server', check the backend logs — it's a server-side fault
Example fix
// before — generic catch, no branching
try { await runQuery(cypher); }
catch (e) { console.error(e.message); }
// after — branch on BackendError code
try { await runQuery(cypher); }
catch (e) {
if (e instanceof BackendError) {
if (e.code === 'unauthorized') promptForToken();
else if (e.code === 'rate_limited') scheduleRetry(e.retryAfterMs);
else showServerError(e.message, e.status);
} else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: probe auth before issuing the real request on a gated deploy
import { fetchServerInfo, BackendError } from './services/backend-client.js';
try { await fetchServerInfo(); }
catch (e) {
if (e instanceof BackendError && e.code === 'unauthorized') promptForToken();
} Type guard
import { BackendError } from './services/backend-client.js';
function isBackendError(e: unknown): e is BackendError {
return e instanceof BackendError;
}
// Narrow by code:
function isNotFound(e: BackendError): boolean { return e.code === 'not_found'; }
function isUnauthorized(e: BackendError): boolean { return e.code === 'unauthorized'; }
function isRateLimited(e: BackendError): boolean { return e.code === 'rate_limited'; } Try / catch
try {
await fetchData();
} catch (e) {
if (e instanceof BackendError) {
switch (e.code) {
case 'unauthorized': promptForToken(); break;
case 'origin_blocked': openLocalUi(); break;
case 'rate_limited': scheduleRetry(e.retryAfterMs); break;
case 'not_found': showNotFound(e.message); break;
case 'client': showClientError(e.message, e.status); break;
case 'server': showServerError(e.message, e.status); break;
}
} else throw e;
} Prevention
- Always branch on BackendError.code rather than parsing the message — codes are stable, messages aren't
- For 429, honor e.retryAfterMs (parsed from Retry-After) before retrying
- For 401 unauthorized, prompt for the deploy access token; for 403 origin_blocked, open the local UI
- Inspect e.status for HTTP-specific handling (e.g. 404 vs 409)
When it happens
Trigger: Any fetch returning response.ok === false: a 400 (bad request body), 401 (auth), 403 (forbidden/origin blocked), 404 (not found), 409 (conflict), 429 (rate limited), 500/502/503 (server error). assertOk parses the JSON body for an error/message/code field and constructs the appropriate BackendError.
Common situations: 404 when the repo isn't indexed; 401 when the deploy access token is missing/wrong on a gated deploy; 403 origin_not_allowed when hitting a write route from a different host; 429 when express-rate-limit trips; 500 when the backend hits an internal error.
Related errors
- Request failed after retries (HTTP ${response.status})
- server
- --allow-insecure-connection / ${LLM_ALLOW_INSECURE_CONNECTIO
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/760f771d28bc6a6b.
Report an issue: GitHub.