{"record":{"id":"3d71684bfaf6b026","repo":"Stirling-Tools/Stirling-PDF","slug":"failed-to-download-avatar-response-status-re","errorCode":null,"errorMessage":"Failed to download avatar: ${response.status} ${response.statusText}","messagePattern":"Failed to download avatar: (.+?) (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"frontend/editor/src/saas/services/avatarSyncService.ts","lineNumber":73,"sourceCode":"  }\n}\n\n/**\n * Download and optimize an avatar image\n * Resizes to 256x256 and converts to PNG format\n * @param url Avatar URL from OAuth provider\n * @returns Optimized image blob\n */\nexport async function downloadAndOptimizeAvatar(url: string): Promise<Blob> {\n  try {\n    // 1. Fetch image from provider URL\n    const response = await fetch(url, {\n      mode: \"cors\",\n      credentials: \"omit\",\n    });\n\n    if (!response.ok) {\n      throw new Error(\n        `Failed to download avatar: ${response.status} ${response.statusText}`,\n      );\n    }\n\n    const blob = await response.blob();\n\n    // 2. Create image bitmap\n    const img = await createImageBitmap(blob);\n\n    // 3. Create canvas and draw scaled image\n    const canvas = document.createElement(\"canvas\");\n    canvas.width = AVATAR_SIZE;\n    canvas.height = AVATAR_SIZE;\n    const ctx = canvas.getContext(\"2d\");\n\n    if (!ctx) {\n      throw new Error(\"Failed to get canvas context\");\n    }","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/saas/services/avatarSyncService.ts#L55-L91","documentation":"Thrown by downloadAndOptimizeAvatar() when fetch() to the OAuth provider's avatar URL returns a non-OK HTTP status. The error message includes the status code and status text for diagnosis. The fetch uses mode:'cors' and credentials:'omit' to comply with provider CORS policies. The caller syncOAuthAvatar() catches all errors and returns false, so this degrades gracefully to the user's existing picture or initials.","triggerScenarios":"The avatar URL from OAuth provider metadata has expired or changed (GitHub avatar CDN URLs can rotate); the provider rate-limits unauthenticated image fetches (HTTP 429); the URL returns 403/404; CORS policy on the provider's CDN blocks the cross-origin request.","commonSituations":"Stale avatar URL cached in Supabase user_metadata after the provider rotated CDN links; GitHub/Google avatar CDN under rate limiting; provider image endpoint temporarily down; network proxy/firewall blocking the provider's image domain.","solutions":["Check the specific HTTP status code in the error message (403, 404, 429, 5xx)","For expired URLs, re-authenticate via OAuth to get a fresh avatar URL in user_metadata","Add retry with exponential backoff for 429 and 5xx status codes","Fall back to the existing stored avatar or user initials (syncOAuthAvatar already does this)"],"exampleFix":"// before\nif (!response.ok) {\n  throw new Error(`Failed to download avatar: ${response.status} ${response.statusText}`);\n}\n\n// after\nif (!response.ok) {\n  if (response.status === 429 || response.status >= 500) {\n    await new Promise((r) => setTimeout(r, 1000));\n    return downloadAndOptimizeAvatar(url); // retry once\n  }\n  throw new Error(`Failed to download avatar: ${response.status} ${response.statusText}`);\n}","handlingStrategy":"fallback","validationCode":"// Validate the avatar URL is reachable before full processing\n// (syncOAuthAvatar already catches and degrades gracefully)\nconst url = getProviderAvatarUrl(user);\nif (!url || !url.startsWith('https://')) {\n  return false; // skip sync, use existing avatar\n}","typeGuard":null,"tryCatchPattern":"// syncOAuthAvatar already wraps in try/catch and returns false on failure:\n// The caller should not throw — just check the boolean result:\nconst synced = await syncOAuthAvatar(user);\nif (!synced) {\n  // Gracefully degrade to existing avatar or initials — no error UI needed\n}","preventionTips":["Re-authenticate via OAuth periodically to get fresh avatar URLs before they expire","Add retry with backoff for transient 429/5xx from provider CDNs","Cache the optimized avatar blob locally so expired source URLs don't cause repeated failures","Treat avatar sync as best-effort — never block user flows on its success"],"tags":["avatar","oauth","network","cors","saas"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}