BloopAI/vibe-kanban · error

(dynamic: extractErrorMessage(response, fallbackMessage))

Error message

(dynamic: extractErrorMessage(response, fallbackMessage))

What it means

parseLocalApiResponse throws Error(extractErrorMessage(response, fallbackMessage)) when the local/relay session API responds with a non-2xx status. The message is dynamic: it prefers the JSON body's `message` or `error` field, else falls back to the caller's fallback string with the HTTP status appended.

Source

Thrown at packages/web-core/src/shared/lib/relayBackendApi.ts:199

interface LocalApiSuccess<T> {
  success: true;
  data: T;
}

interface LocalApiFailure {
  success: false;
  message?: string;
}

type LocalApiEnvelope<T> = LocalApiSuccess<T> | LocalApiFailure;

async function parseLocalApiResponse<T>(
  response: Response,
  fallbackMessage: string
): Promise<T> {
  if (!response.ok) {
    throw new Error(await extractErrorMessage(response, fallbackMessage));
  }

  const body = (await response.json()) as LocalApiEnvelope<T>;
  if (!body.success) {
    throw new Error(body.message || fallbackMessage);
  }

  return body.data;
}

async function extractErrorMessage(
  response: Response,
  fallbackMessage: string
): Promise<string> {
  try {
    const body = await response.json();
    if (body && typeof body.message === 'string') {
      return body.message;

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Log response.status and the extracted body message to identify the server-side cause.
  2. Verify hostId and browser sessionId are valid and the relay session is still alive before retrying.
  3. Check frontend/backend version alignment for the StartSpake2/FinishSpake2 request shapes.
  4. Surface the message to the user and restart the pairing flow from scratch (PAKE state is single-use).

Example fix

// before
const res = await startRelaySpake2Enrollment(hostId, sessionId, payload);
// after
let res;
try {
  res = await startRelaySpake2Enrollment(hostId, sessionId, payload);
} catch (e) {
  showToast(e.message); // server-provided message or 'Failed to start pairing. (400)'
  restartPairingFlow();
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-validate inputs the server checks
if (!hostId || !sessionId) throw new Error('hostId/sessionId required');
if (!payload) throw new Error('payload required');

Type guard

function isHttpError(e: unknown, statusHint?: number): e is Error {
  return e instanceof Error && (statusHint === undefined || /\(\d{3}\)/.test(e.message));
}

Try / catch

try {
  const res = await startRelaySpake2Enrollment(hostId, sessionId, payload);
} catch (e) {
  if (e instanceof Error) {
    showToast(e.message); // server message or 'Failed to start pairing. (NNN)'
    restartPairing();
  }
}

Prevention

When it happens

Trigger: startRelaySpake2Enrollment, finishRelaySpake2Enrollment, or refreshRelaySigningSession gets a non-ok Response (e.g. 400 bad PAKE payload, 404 wrong host/session, 409 pairing state mismatch, 500 server error) from /api/relay-auth/... endpoints.

Common situations: Server rejects the SPAKE2 flow mid-pairing; wrong hostId/sessionId in the relay URL; server not running the relay-auth endpoints (404); request body schema drift between frontend and backend versions.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/b3ced83049e4891b. Report an issue: GitHub.