{"record":{"id":"f7961041d8e571c2","repo":"BloopAI/vibe-kanban","slug":"body-message-fallbackmessage-dynamic","errorCode":null,"errorMessage":"body.message || fallbackMessage (dynamic)","messagePattern":"body\\.message \\|\\| fallbackMessage \\(dynamic\\)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/web-core/src/shared/lib/relayBackendApi.ts","lineNumber":204,"sourceCode":"\ninterface LocalApiFailure {\n  success: false;\n  message?: string;\n}\n\ntype LocalApiEnvelope<T> = LocalApiSuccess<T> | LocalApiFailure;\n\nasync function parseLocalApiResponse<T>(\n  response: Response,\n  fallbackMessage: string\n): Promise<T> {\n  if (!response.ok) {\n    throw new Error(await extractErrorMessage(response, fallbackMessage));\n  }\n\n  const body = (await response.json()) as LocalApiEnvelope<T>;\n  if (!body.success) {\n    throw new Error(body.message || fallbackMessage);\n  }\n\n  return body.data;\n}\n\nasync function extractErrorMessage(\n  response: Response,\n  fallbackMessage: string\n): Promise<string> {\n  try {\n    const body = await response.json();\n    if (body && typeof body.message === 'string') {\n      return body.message;\n    }\n    if (body && typeof body.error === 'string') {\n      return body.error;\n    }\n  } catch {","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/lib/relayBackendApi.ts#L186-L222","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Read body.message for the server's specific rejection reason (e.g. invalid PAKE proof).","Verify the server returns the LocalApiEnvelope shape ({success:true,data} | {success:false,message}).","Restart the enrollment/pairing flow — server-side session state is typically consumed on failure.","Check server logs for the pairing session to confirm the cryptographic rejection cause."],"exampleFix":"// before\nconst data = await refreshRelaySigningSession(hostId, sessionId, payload);\n// after\ntry {\n  const data = await refreshRelaySigningSession(hostId, sessionId, payload);\n} catch (e) {\n  if (e.message !== 'Failed to refresh relay signing session.') {\n    console.warn('Server rejected refresh:', e.message);\n  }\n  scheduleReauth();\n}","handlingStrategy":"type-guard","validationCode":"// validate server response shape before trusting it\nfunction isEnvelope(v: unknown): v is { success: boolean; message?: string; data?: unknown } {\n  return typeof v === 'object' && v !== null && 'success' in v;\n}","typeGuard":"function isFailureEnvelope(b: { success: boolean; message?: string }): b is { success: false; message?: string } {\n  return b.success === false;\n}","tryCatchPattern":"try {\n  const data = await finishRelaySpake2Enrollment(hostId, sessionId, payload);\n} catch (e) {\n  if (e instanceof Error && e.message !== 'Failed to finish pairing.') {\n    console.warn('Server rejected:', e.message);\n  }\n  restartPairing();\n}","preventionTips":["Confirm the server returns the {success, data|message} envelope on 200s","Don't reuse consumed PAKE session state — always start fresh","Add integration tests covering success:false envelopes","Pin client and server to matching protocol versions"],"tags":["api-response","envelope","pairing"],"backgroundTag":"http-api-error-response","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}