{"record":{"id":"ef01357ef96eb7fa","repo":"BloopAI/vibe-kanban","slug":"invitation-not-found-res-status","errorCode":null,"errorMessage":"Invitation not found (${res.status})","messagePattern":"Invitation not found \\((.+?)\\)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/remote-web/src/shared/lib/api.ts","lineNumber":120,"sourceCode":"  password: string,\n): Promise<LocalLoginResponse> {\n  const res = await fetch(`${API_BASE}/v1/auth/local/login`, {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\" },\n    body: JSON.stringify({ email, password }),\n  });\n  if (!res.ok) {\n    throw new Error(`Local login failed (${res.status})`);\n  }\n  return res.json();\n}\n\nexport async function getInvitation(\n  token: string,\n): Promise<InvitationLookupResponse> {\n  const res = await fetch(`${API_BASE}/v1/invitations/${token}`);\n  if (!res.ok) {\n    throw new Error(`Invitation not found (${res.status})`);\n  }\n  return res.json();\n}\n\nexport async function acceptInvitation(\n  token: string,\n  accessToken: string,\n): Promise<AcceptInvitationResponse> {\n  const res = await fetch(`${API_BASE}/v1/invitations/${token}/accept`, {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/json\",\n      Authorization: `Bearer ${accessToken}`,\n    },\n  });\n  if (!res.ok) {\n    throw new Error(`Failed to accept invitation (${res.status})`);\n  }","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/remote-web/src/shared/lib/api.ts#L102-L138","documentation":"getInvitation looks up a pending invitation by its token at GET {API_BASE}/v1/invitations/{token}. The function throws this Error whenever the HTTP response is not ok (res.ok false), regardless of status code. It is a plain Error with only the status number embedded in the message, so callers cannot branch on the status programmatically.","triggerScenarios":"GET /v1/invitations/{token} returns 404 (token unknown, invitation already accepted or revoked), 410 (expired invitation per expires_at), or any 4xx/5xx from the remote server. Also fires when API_BASE (VITE_API_BASE_URL) is wrong and the request hits a route that doesn't exist.","commonSituations":"User opens an invitation link after the invite expired; user re-opens a link after already accepting the invite; someone mangles the token in the URL; the remote-web app points at the wrong backend URL so the route 404s.","solutions":["Ask the organization admin to resend a fresh invitation and open the new link.","Verify the token in the URL matches the invitation exactly (no truncation/copy errors).","Check VITE_API_BASE_URL points at the running remote server; a wrong base makes every lookup 404.","Inspect res.status in the message: 404 = not found/already used, 410 = expired, 5xx = server-side issue to report."],"exampleFix":"// before: single generic throw, status only in message\nif (!res.ok) {\n  throw new Error(`Invitation not found (${res.status})`);\n}\n// after: attach status and clearer message per case\nif (!res.ok) {\n  const err = new Error(\n    res.status === 404\n      ? \"Invitation not found, already used, or revoked\"\n      : res.status === 410\n        ? \"This invitation has expired\"\n        : `Invitation lookup failed (${res.status})`,\n  );\n  (err as Error & { status: number }).status = res.status;\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":"if (typeof token !== 'string' || token.trim() === '') {\n  throw new Error('Invitation token is missing or empty');\n}\nif (!import.meta.env.VITE_API_BASE_URL && import.meta.env.PROD) {\n  console.warn('VITE_API_BASE_URL is not set; requests go to same origin');\n}","typeGuard":"function isInvitationLookup(v: unknown): v is InvitationLookupResponse {\n  const r = v as InvitationLookupResponse;\n  return (\n    !!r && typeof r.id === 'string' && typeof r.organization_slug === 'string' &&\n    typeof r.role === 'string' && typeof r.expires_at === 'string'\n  );\n}","tryCatchPattern":"try {\n  const invitation = await getInvitation(token);\n  // render invitation details\n} catch (e) {\n  const status = Number(/\\((\\d{3})\\)$/.exec((e as Error).message)?.[1]);\n  if (status === 404) show('Invitation not found or already used');\n  else if (status === 410) show('This invitation has expired');\n  else show('Could not load the invitation. Please try the link again.');\n}","preventionTips":["Validate the token is present and well-formed before calling the API.","Render a friendly expired/used-invitation screen instead of a raw error.","Warn users that invitation links are single-use and time-limited.","Set VITE_API_BASE_URL explicitly per environment to avoid 404s from a wrong origin."],"tags":["http","api","invitation","not-found"],"backgroundTag":"invitation-not-found","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}