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

  1. Inspect error.status and error.code to determine the category (client/server/not_found/unauthorized/etc.)
  2. For code 'unauthorized', prompt the user for the deploy access token and retry
  3. For code 'origin_blocked', open the local UI on the same host as the backend
  4. For code 'rate_limited', honor error.retryAfterMs before retrying
  5. 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

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


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/760f771d28bc6a6b. Report an issue: GitHub.