{"record":{"id":"ad54063956b853f9","repo":"Significant-Gravitas/AutoGPT","slug":"failed-to-update-email","errorCode":null,"errorMessage":"Failed to update email","messagePattern":"Failed to update email","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"autogpt_platform/frontend/src/app/(platform)/profile/(user)/settings/components/SettingsForm/components/EmailForm/useEmailForm.ts","lineNumber":41,"sourceCode":"// Better Auth owns the email change. For a verified user it emails a\n// confirmation link to their CURRENT address and only applies the new email\n// once that link is clicked (anti-takeover); for an unverified user the change\n// applies immediately. Platform User.email (notifications / Stripe) then\n// converges via the databaseHooks.user.update mirror in lib/auth/auth.ts — we\n// deliberately do NOT write the platform email here, so it can never diverge to\n// an unverified value.\nasync function requestEmailChange(email: string) {\n  const response = await fetch(\"/api/auth/user\", {\n    method: \"PUT\",\n    headers: {\n      \"Content-Type\": \"application/json\",\n    },\n    body: JSON.stringify({ email }),\n  });\n\n  if (!response.ok) {\n    const error = await response.json();\n    throw new Error(error.error || \"Failed to update email\");\n  }\n\n  return response.json();\n}\n\nexport function useEmailForm({ user }: { user: User }) {\n  const { toast } = useToast();\n  const defaultValues = createEmailDefaultValues(user);\n  const currentEmail = user.email;\n  const [isSubmitting, setIsSubmitting] = useState(false);\n\n  const form = useForm<z.infer<typeof emailFormSchema>>({\n    resolver: zodResolver(emailFormSchema),\n    defaultValues,\n    mode: \"onSubmit\",\n  });\n\n  async function onSubmit(values: z.infer<typeof emailFormSchema>) {","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/Significant-Gravitas/AutoGPT/blob/9c8bb5550f446ba5d3046b78896578742495b3cf/autogpt_platform/frontend/src/app/(platform)/profile/(user)/settings/components/SettingsForm/components/EmailForm/useEmailForm.ts#L23-L59","documentation":"Fallback message from requestEmailChange in the profile settings EmailForm: it PUTs {email} to the Next.js route /api/auth/user and, on a non-ok response, tries to surface error.error from the JSON body, falling back to 'Failed to update email' when the body has no error field or fails to parse. Note: unlike its sibling in useAccountCard (error 30), this version does NOT .catch() the response.json() call — a non-JSON error body throws a SyntaxError here instead, shadowing this message.","triggerScenarios":"PUT /api/auth/user returning 400 (email already in use, invalid format), 401 (session expired), 429 (Supabase rate limit on email changes), or a non-JSON body (HTML 500 page from the route) — the last case throws a JSON parse error rather than this message.","commonSituations":"Changing email to one already registered; Supabase auth rate-limiting repeated change attempts; expired session in a long-open settings tab; dev misconfiguration of the auth route.","solutions":["Check the PUT /api/auth/user response in DevTools — the Supabase error string (error.error) is usually shown; the generic message means the body was empty/HTML.","If 401, sign in again before retrying.","If 429, wait — Supabase limits email-change frequency.","Harden requestEmailChange with response.json().catch(() => ({})) as its sibling useAccountCard already does, so non-JSON bodies produce this message instead of a SyntaxError."],"exampleFix":"// before\nconst error = await response.json();\nthrow new Error(error.error || \"Failed to update email\");\n\n// after\nconst error = await response.json().catch(() => ({}));\nthrow new Error(error.error || \"Failed to update email\");","handlingStrategy":"try-catch","validationCode":"const emailSchema = z.string().email();\nconst parsed = emailSchema.safeParse(email);\nif (!parsed.success) { /* show field error, skip submit */ }","typeGuard":"function isEmailUpdateFailure(err: unknown): boolean {\n  return err instanceof Error && err.message === \"Failed to update email\";\n}","tryCatchPattern":"try {\n  await requestEmailChange(email);\n} catch (error) {\n  // error.message is usually the Supabase error string; generic fallback\n  toast({ description: (error as Error).message, variant: \"destructive\" });\n}","preventionTips":["Validate email format client-side (zod) before the PUT to avoid obvious 400s.","Always parse error bodies defensively: response.json().catch(() => ({})) — the sibling useAccountCard shows the correct pattern.","Debounce/disable submit to avoid Supabase rate limits on repeated attempts."],"tags":["email","settings","supabase","http-status","frontend"],"backgroundTag":null,"analyzedSha":"9c8bb5550f446ba5d3046b78896578742495b3cf","analyzedAt":"2026-08-14T17:17:21.957Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}