{"record":{"id":"90b128f3e88af06c","repo":"BloopAI/vibe-kanban","slug":"errormessage-dynamic-body-error-body-message","errorCode":null,"errorMessage":"errorMessage (dynamic: body.error || body.message || response.statusText)","messagePattern":"errorMessage \\(dynamic: body\\.error \\|\\| body\\.message \\|\\| response\\.statusText\\)","errorType":"http","errorClass":"ApiError","httpStatus":null,"severity":"error","filePath":"packages/web-core/src/shared/lib/api.ts","lineNumber":1338,"sourceCode":"  const { tokenManager } = await import('@/shared/lib/auth/tokenManager');\n  return tokenManager.getToken();\n}\n\nconst handleRemoteResponse = async <T>(response: Response): Promise<T> => {\n  if (!response.ok) {\n    let errorMessage = `Request failed with status ${response.status}`;\n\n    try {\n      const body = (await response.json()) as {\n        error?: string;\n        message?: string;\n      };\n      errorMessage = body.error || body.message || errorMessage;\n    } catch {\n      errorMessage = response.statusText || errorMessage;\n    }\n\n    throw new ApiError(errorMessage, response.status, response);\n  }\n\n  if (response.status === 204) {\n    return undefined as T;\n  }\n\n  return response.json() as Promise<T>;\n};\n\n// Organizations API\nexport const organizationsApi = {\n  getMembers: async (\n    orgId: string\n  ): Promise<OrganizationMemberWithProfile[]> => {\n    const response = await makeRemoteRequest(\n      `/v1/organizations/${orgId}/members`\n    );\n    const result = await handleRemoteResponse<ListMembersResponse>(response);","sourceCodeStart":1320,"sourceCodeEnd":1356,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/lib/api.ts#L1320-L1356","documentation":"handleRemoteResponse is the shared response handler for remote-server API calls; when response.ok is false it tries to parse a JSON body for `error` or `message`, falls back to statusText, and throws ApiError(errorMessage, status, response). This is the generic 'remote API returned a non-2xx status' error carrying the server-provided message when available.","triggerScenarios":"Any makeRemoteRequest call (organizations, members, etc.) that receives a 4xx/5xx response: invalid org ID (404), insufficient permissions (403), validation rejection (422), server crash (500), or a non-JSON body (message falls back to statusText).","commonSituations":"Using a stale org ID after it was deleted; calling a remote endpoint with an expired token; backend deployed with a breaking API change returning unexpected error shape; network proxy returning HTML error pages (empty error message).","solutions":["Inspect e.status on the thrown ApiError and branch (401 → re-auth, 403 → permissions, 404 → check the resource ID).","Read the parsed body (e.response) for the server's `error`/`message` field to learn the exact rejection reason.","Retry transient 5xx with backoff; do not retry 4xx.","Confirm the remote server version matches the client API version (shared/remote-types)."],"exampleFix":"// before\nconst members = await organizationsApi.getMembers(orgId);\n// after\ntry {\n  const members = await organizationsApi.getMembers(orgId);\n} catch (e) {\n  if (e instanceof ApiError) {\n    if (e.status === 401) return reauth();\n    console.error('Remote API error', e.status, e.message, e.response);\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Ensure a session exists before remote calls\nconst s = await oauthApi.status();\nif (!s.authenticated) await login();","typeGuard":"function isApiError(e: unknown): e is ApiError {\n  return e instanceof ApiError;\n}","tryCatchPattern":"try {\n  const data = await someRemoteApi.call();\n} catch (e) {\n  if (isApiError(e)) {\n    if (e.status === 401) return reauth();\n    if (e.status >= 500) return retryWithBackoff();\n    showUserError(e.message);\n    return;\n  }\n  throw e;\n}","preventionTips":["Always catch ApiError around remote calls and branch on e.status","Inspect e.response body for the server's detailed message","Pin client/server API versions together (shared/remote-types)","Retry only 5xx and network errors, never 4xx"],"tags":["api","http-error","remote"],"backgroundTag":"api-non-2xx-response","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}