BloopAI/vibe-kanban · error · ApiError
errorMessage (dynamic: body.error || body.message || respons
Error message
errorMessage (dynamic: body.error || body.message || response.statusText)
What it means
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.
Source
Thrown at packages/web-core/src/shared/lib/api.ts:1338
const { tokenManager } = await import('@/shared/lib/auth/tokenManager');
return tokenManager.getToken();
}
const handleRemoteResponse = async <T>(response: Response): Promise<T> => {
if (!response.ok) {
let errorMessage = `Request failed with status ${response.status}`;
try {
const body = (await response.json()) as {
error?: string;
message?: string;
};
errorMessage = body.error || body.message || errorMessage;
} catch {
errorMessage = response.statusText || errorMessage;
}
throw new ApiError(errorMessage, response.status, response);
}
if (response.status === 204) {
return undefined as T;
}
return response.json() as Promise<T>;
};
// Organizations API
export const organizationsApi = {
getMembers: async (
orgId: string
): Promise<OrganizationMemberWithProfile[]> => {
const response = await makeRemoteRequest(
`/v1/organizations/${orgId}/members`
);
const result = await handleRemoteResponse<ListMembersResponse>(response);View on GitHub (pinned to 4deb7eca8f)
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).
Example fix
// before
const members = await organizationsApi.getMembers(orgId);
// after
try {
const members = await organizationsApi.getMembers(orgId);
} catch (e) {
if (e instanceof ApiError) {
if (e.status === 401) return reauth();
console.error('Remote API error', e.status, e.message, e.response);
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure a session exists before remote calls const s = await oauthApi.status(); if (!s.authenticated) await login();
Type guard
function isApiError(e: unknown): e is ApiError {
return e instanceof ApiError;
} Try / catch
try {
const data = await someRemoteApi.call();
} catch (e) {
if (isApiError(e)) {
if (e.status === 401) return reauth();
if (e.status >= 500) return retryWithBackoff();
showUserError(e.message);
return;
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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).
Related errors
- Invitation not found (${res.status})
- Failed to accept invitation (${res.status})
- Failed to list organizations (${res.status})
- Failed to fetch identity (${res.status})
- Failed to list projects (${res.status})
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/90b128f3e88af06c.
Report an issue: GitHub.