{"record":{"id":"cf02df140681a2f4","repo":"BloopAI/vibe-kanban","slug":"failed-to-fetch-identity-res-status","errorCode":null,"errorMessage":"Failed to fetch identity (${res.status})","messagePattern":"Failed to fetch identity \\((.+?)\\)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/remote-web/src/shared/lib/api.ts","lineNumber":207,"sourceCode":"      method: \"POST\",\n    });\n  } finally {\n    await clearTokens();\n  }\n}\n\nexport async function listOrganizations(): Promise<ListOrganizationsResponse> {\n  const res = await authenticatedFetch(`${API_BASE}/v1/organizations`);\n  if (!res.ok) {\n    throw new Error(`Failed to list organizations (${res.status})`);\n  }\n  return res.json();\n}\n\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;","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/remote-web/src/shared/lib/api.ts#L189-L225","documentation":"getIdentity fetches GET {API_BASE}/v1/identity via authenticatedFetch to retrieve the current user's id/username/email. Non-ok responses after the built-in single 401-refresh retry throw this Error. It is commonly surfaced through TanStack-style queries (identityQuery), where it appears as the query's error.","triggerScenarios":"GET /v1/identity returns 401 after the automatic refresh also failed (refresh token expired/revoked), 403 (account disabled), or 5xx from the remote server. Also when the API base URL is wrong and the endpoint doesn't exist.","commonSituations":"App restored with stale tokens after server-side token revocation or a server database reset; backend redeployed/invalidating sessions; user account removed while a tab kept old tokens.","solutions":["Redirect the user to login: clear stored tokens and re-authenticate.","Check status: 401 after retry = tokens unusable (re-login); 403 = account access issue; 5xx = backend problem, check server logs.","Verify VITE_API_BASE_URL points at the correct remote API.","In the consuming query, handle the error state gracefully instead of crashing the identity screen."],"exampleFix":"// before\nif (!res.ok) {\n  throw new Error(`Failed to fetch identity (${res.status})`);\n}\n// after\nif (!res.ok) {\n  const err = new Error(`Failed to fetch identity (${res.status})`);\n  (err as Error & { status: number }).status = res.status;\n  if (res.status === 401) await clearTokens();\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":"const accessToken = await getToken();\nif (!accessToken) {\n  redirectToLogin();\n}","typeGuard":"function isIdentityResponse(v: unknown): v is IdentityResponse {\n  const r = v as IdentityResponse;\n  return !!r && typeof r.user_id === 'string' && typeof r.email === 'string';\n}","tryCatchPattern":"const identityQuery = useQuery({\n  queryKey: ['identity'],\n  queryFn: getIdentity,\n  retry: (count, err) => {\n    const status = Number(/\\((\\d{3})\\)$/.exec((err as Error).message)?.[1]);\n    return status >= 500 && count < 2; // never retry auth failures\n  },\n});\nif (identityQuery.error) {\n  const msg = (identityQuery.error as Error).message;\n  if (msg.includes('(401)')) redirectToLogin();\n}","preventionTips":["Configure the identity query to not retry on 4xx and to redirect to login on 401.","Clear stale tokens on app boot when identity fails with 401.","Show a 'session expired' notice rather than a blank/broken page.","Verify VITE_API_BASE_URL after each backend redeploy."],"tags":["http","api","auth","identity"],"backgroundTag":"jwt-token-expired","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}