{"record":{"id":"0fb9b54705d1093b","repo":"BloopAI/vibe-kanban","slug":"failed-to-list-projects-res-status","errorCode":null,"errorMessage":"Failed to list projects (${res.status})","messagePattern":"Failed to list projects \\((.+?)\\)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/remote-web/src/shared/lib/api.ts","lineNumber":221,"sourceCode":"\nexport async function getIdentity(): Promise<IdentityResponse> {\n  const res = await authenticatedFetch(`${API_BASE}/v1/identity`);\n  if (!res.ok) {\n    throw new Error(`Failed to fetch identity (${res.status})`);\n  }\n  return res.json();\n}\n\nexport async function listOrganizationProjects(\n  organizationId: string,\n): Promise<Project[]> {\n  const params = new URLSearchParams({\n    organization_id: organizationId,\n  });\n\n  const res = await authenticatedFetch(`${API_BASE}/v1/projects?${params}`);\n  if (!res.ok) {\n    throw new Error(`Failed to list projects (${res.status})`);\n  }\n\n  const body = (await res.json()) as { projects: Project[] };\n  return body.projects;\n}\n","sourceCodeStart":203,"sourceCodeEnd":227,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/remote-web/src/shared/lib/api.ts#L203-L227","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: called with possibly-empty id\nconst projects = await listOrganizationProjects(orgId);\n// after: validate before calling\nif (!orgId) throw new Error(\"organizationId is required\");\nconst projects = await listOrganizationProjects(orgId);","handlingStrategy":"validation","validationCode":"function canListProjects(orgId: unknown): orgId is string {\n  return typeof orgId === 'string' && orgId.trim() !== '';\n}\n// usage\nif (!canListProjects(organizationId)) return; // skip fetch until an org is selected","typeGuard":"function isProjectArray(v: unknown): v is Project[] {\n  const b = v as { projects?: unknown };\n  return !!b && Array.isArray(b.projects) &&\n    b.projects.every((p) => !!p && typeof (p as Project).id === 'string');\n}","tryCatchPattern":"if (!organizationId) return; // never call with an empty id\ntry {\n  const projects = await listOrganizationProjects(organizationId);\n  setProjects(projects);\n} catch (e) {\n  const status = Number(/\\((\\d{3})\\)$/.exec((e as Error).message)?.[1]);\n  if (status === 403) show('You no longer have access to this organization');\n  else show('Failed to load projects. Retrying…');\n}","preventionTips":["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."],"tags":["http","api","projects","validation"],"backgroundTag":"api-request-failed","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}