{"record":{"id":"b3ced83049e4891b","repo":"BloopAI/vibe-kanban","slug":"dynamic-extracterrormessage-response-fallbackme","errorCode":null,"errorMessage":"(dynamic: extractErrorMessage(response, fallbackMessage))","messagePattern":"\\(dynamic: extractErrorMessage\\(response, fallbackMessage\\)\\)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/web-core/src/shared/lib/relayBackendApi.ts","lineNumber":199,"sourceCode":"\ninterface LocalApiSuccess<T> {\n  success: true;\n  data: T;\n}\n\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;","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/lib/relayBackendApi.ts#L181-L217","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log response.status and the extracted body message to identify the server-side cause.","Verify hostId and browser sessionId are valid and the relay session is still alive before retrying.","Check frontend/backend version alignment for the StartSpake2/FinishSpake2 request shapes.","Surface the message to the user and restart the pairing flow from scratch (PAKE state is single-use)."],"exampleFix":"// before\nconst res = await startRelaySpake2Enrollment(hostId, sessionId, payload);\n// after\nlet res;\ntry {\n  res = await startRelaySpake2Enrollment(hostId, sessionId, payload);\n} catch (e) {\n  showToast(e.message); // server-provided message or 'Failed to start pairing. (400)'\n  restartPairingFlow();\n}","handlingStrategy":"try-catch","validationCode":"// pre-validate inputs the server checks\nif (!hostId || !sessionId) throw new Error('hostId/sessionId required');\nif (!payload) throw new Error('payload required');","typeGuard":"function isHttpError(e: unknown, statusHint?: number): e is Error {\n  return e instanceof Error && (statusHint === undefined || /\\(\\d{3}\\)/.test(e.message));\n}","tryCatchPattern":"try {\n  const res = await startRelaySpake2Enrollment(hostId, sessionId, payload);\n} catch (e) {\n  if (e instanceof Error) {\n    showToast(e.message); // server message or 'Failed to start pairing. (NNN)'\n    restartPairing();\n  }\n}","preventionTips":["Verify hostId/sessionId are valid before building the relay URL","Keep frontend/backend request schema versions in sync","Treat PAKE sessions as single-use: restart from start on any failure","Log response status alongside the error for diagnosis"],"tags":["http-error","api-response","pairing"],"backgroundTag":"http-api-error-response","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}