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

  1. Guard the caller: only invoke with a non-empty organizationId from a successfully loaded organization.
  2. If status is 403, confirm the user still belongs to the organization and has an appropriate role.
  3. If status is 401, re-authenticate (refresh retry already failed).
  4. 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

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


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/0fb9b54705d1093b. Report an issue: GitHub.