BloopAI/vibe-kanban · error · Error
Failed to list projects (${res.status})
Error message
Failed to list projects (${res.status}) What it means
listOrganizationProjects fetches GET {API_BASE}/v1/projects?organization_id={id} via authenticatedFetch and unwraps { projects: Project[] }. Any non-ok response throws this Error with the status in the message. The 401-refresh retry is built in, so failures here are usually permission- or server-related.
Source
Thrown at packages/remote-web/src/shared/lib/api.ts:221
export async function getIdentity(): Promise<IdentityResponse> {
const res = await authenticatedFetch(`${API_BASE}/v1/identity`);
if (!res.ok) {
throw new Error(`Failed to fetch identity (${res.status})`);
}
return res.json();
}
export async function listOrganizationProjects(
organizationId: string,
): Promise<Project[]> {
const params = new URLSearchParams({
organization_id: organizationId,
});
const res = await authenticatedFetch(`${API_BASE}/v1/projects?${params}`);
if (!res.ok) {
throw new Error(`Failed to list projects (${res.status})`);
}
const body = (await res.json()) as { projects: Project[] };
return body.projects;
}
View on GitHub (pinned to 4deb7eca8f)
Solutions
- Guard the caller: only invoke with a non-empty organizationId from a successfully loaded organization.
- If status is 403, confirm the user still belongs to the organization and has an appropriate role.
- If status is 401, re-authenticate (refresh retry already failed).
- If 5xx, check remote server health/logs and retry later.
Example fix
// before: called with possibly-empty id
const projects = await listOrganizationProjects(orgId);
// after: validate before calling
if (!orgId) throw new Error("organizationId is required");
const projects = await listOrganizationProjects(orgId); Defensive patterns
Strategy: validation
Validate before calling
function canListProjects(orgId: unknown): orgId is string {
return typeof orgId === 'string' && orgId.trim() !== '';
}
// usage
if (!canListProjects(organizationId)) return; // skip fetch until an org is selected Type guard
function isProjectArray(v: unknown): v is Project[] {
const b = v as { projects?: unknown };
return !!b && Array.isArray(b.projects) &&
b.projects.every((p) => !!p && typeof (p as Project).id === 'string');
} Try / catch
if (!organizationId) return; // never call with an empty id
try {
const projects = await listOrganizationProjects(organizationId);
setProjects(projects);
} catch (e) {
const status = Number(/\((\d{3})\)$/.exec((e as Error).message)?.[1]);
if (status === 403) show('You no longer have access to this organization');
else show('Failed to load projects. Retrying…');
} Prevention
- Only fetch projects after an organization is selected and its id is known-good.
- React to membership changes: on 403, drop the org from the user's cached list.
- Type-check the response body before trusting body.projects.
- Retry only 5xx responses with backoff; surface 401/403 as auth/permission states.
When it happens
Trigger: GET /v1/projects returns 401 after the refresh retry (both tokens stale), 403 (user is not a member of that organization or lacks role), 404/422 (organization_id unknown or malformed), or 5xx backend error. An empty/invalid organizationId produces a query the server rejects.
Common situations: UI renders the projects list before an organization is selected, sending an undefined/empty organization_id; user's membership in the org was revoked; backend outage or proxy 502 while listing projects.
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})
- Host returned HTTP ${response.status}
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/0fb9b54705d1093b.
Report an issue: GitHub.