BloopAI/vibe-kanban · error

body.message || fallbackMessage (dynamic)

Error message

body.message || fallbackMessage (dynamic)

What it means

parseLocalApiResponse also throws when the HTTP status is OK (2xx) but the LocalApiEnvelope body has success:false; the message is body.message or the caller's fallbackMessage. This treats protocol-level failures reported inside a 200 response as errors.

Source

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

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;
    }
    if (body && typeof body.error === 'string') {
      return body.error;
    }
  } catch {

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Read body.message for the server's specific rejection reason (e.g. invalid PAKE proof).
  2. Verify the server returns the LocalApiEnvelope shape ({success:true,data} | {success:false,message}).
  3. Restart the enrollment/pairing flow — server-side session state is typically consumed on failure.
  4. Check server logs for the pairing session to confirm the cryptographic rejection cause.

Example fix

// before
const data = await refreshRelaySigningSession(hostId, sessionId, payload);
// after
try {
  const data = await refreshRelaySigningSession(hostId, sessionId, payload);
} catch (e) {
  if (e.message !== 'Failed to refresh relay signing session.') {
    console.warn('Server rejected refresh:', e.message);
  }
  scheduleReauth();
}
Defensive patterns

Strategy: type-guard

Validate before calling

// validate server response shape before trusting it
function isEnvelope(v: unknown): v is { success: boolean; message?: string; data?: unknown } {
  return typeof v === 'object' && v !== null && 'success' in v;
}

Type guard

function isFailureEnvelope(b: { success: boolean; message?: string }): b is { success: false; message?: string } {
  return b.success === false;
}

Try / catch

try {
  const data = await finishRelaySpake2Enrollment(hostId, sessionId, payload);
} catch (e) {
  if (e instanceof Error && e.message !== 'Failed to finish pairing.') {
    console.warn('Server rejected:', e.message);
  }
  restartPairing();
}

Prevention

When it happens

Trigger: The relay-auth server replies 200 with {success:false, message:'...'} — e.g. SPAKE2 proof verification failure, unknown enrollment session, or signing-session refresh rejected despite the HTTP layer succeeding.

Common situations: Server-side business logic rejection where the backend uses 200 + success flag instead of HTTP status codes; mismatched envelope shape from an older server version that omits `success` (undefined is falsy, so a shape change also triggers this).

Related errors


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