{"record":{"id":"8dec4d3ccdab5aec","repo":"tinyhumansai/openhuman","slug":"failed-to-send-magic-link-response-status","errorCode":null,"errorMessage":"Failed to send magic link (${response.status})","messagePattern":"Failed to send magic link \\((.+?)\\)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/api/authApi.ts","lineNumber":33,"sourceCode":"  email: string,\n  frontendRedirectUri: string,\n  timeoutMs = EMAIL_MAGIC_LINK_TIMEOUT_MS\n): Promise<void> {\n  const backendUrl = await getBackendUrl();\n  const controller = new AbortController();\n  const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);\n\n  try {\n    const versionHeaders = await getClientVersionHeaders();\n    const response = await fetch(`${backendUrl}/auth/email/send-link`, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json', ...versionHeaders },\n      body: JSON.stringify({ email, frontendRedirectUri }),\n      signal: controller.signal,\n    });\n    if (!response.ok) {\n      const body = (await response.json().catch(() => ({}))) as { error?: string };\n      throw new Error(body.error ?? `Failed to send magic link (${response.status})`);\n    }\n  } catch (error) {\n    if (\n      (error instanceof DOMException && error.name === 'AbortError') ||\n      (error instanceof Error && error.name === 'AbortError')\n    ) {\n      throw new Error('Request timed out. Please try again.');\n    }\n    throw error;\n  } finally {\n    window.clearTimeout(timeoutId);\n  }\n}\n\n/**\n * Consume a verified login token and return the JWT.\n * Works for both Telegram and OAuth login tokens.\n * POST /telegram/login-tokens/:token/consume (no auth required)","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/api/authApi.ts#L15-L51","documentation":"POST to <backendUrl>/auth/email/send-link (magic-link email login) returned a non-2xx status, and the JSON body either failed to parse or contained no error field, so the client falls back to a generic message that embeds the HTTP status. The specific status is the main diagnostic.","triggerScenarios":"Backend unreachable-through-proxy returning 502/504; 400 from a malformed email; 429 from rate limiting; 500 from a backend bug — all with a non-JSON or error-less body trigger the fallback text. Bodies that do include error use that message instead.","commonSituations":"Wrong VITE backend URL for the environment (staging URL from a prod build); backend redeploy/down; rate limit hit by repeated resend clicks; CORS/network middleboxes replacing the body with HTML.","solutions":["Check the embedded status: 4xx → fix the request (email format, rate limit); 5xx/502-504 → backend/infra issue, verify the backend URL and service health","Confirm the backend URL config used by the frontend matches the intended environment","Reproduce with curl against the same URL to see the raw body the browser received","Add the response body text to error reporting so the fallback path is observable"],"exampleFix":"// before\nthrow new Error(body.error ?? `Failed to send magic link (${response.status})`);\n\n// after (in caller, capture status for diagnostics)\ntry { await sendMagicLink(email); }\ncatch (e) {\n  const m = /\\((\\d{3})\\)$/.exec(String(e.message));\n  report('magic-link-failed', { status: m?.[1], emailDomain: email.split('@')[1] });\n  showError(e.message);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await sendMagicLink(email); showCheckYourEmail(); }\ncatch (e) {\n  const status = /\\((\\d{3})\\)$/.exec(String((e as Error).message))?.[1];\n  if (status === '429') showNotice('Too many attempts — wait a moment and retry.');\n  else if (status && Number(status) >= 500) showError('Service issue — try again shortly.');\n  else showError((e as Error).message);\n}","preventionTips":["Verify the backend URL env per environment before release builds","Surface the embedded HTTP status in error reporting for triage","Rate-limit the send button client-side to avoid 429s from double clicks"],"tags":["network","http","authentication","backend"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}