{"record":{"id":"5e1c4cd992d7284c","repo":"immich-app/immich","slug":"failed-to-fetch-picture-response-statustext","errorCode":null,"errorMessage":"Failed to fetch picture: ${response.statusText}","messagePattern":"Failed to fetch picture: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/repositories/oauth.repository.ts","lineNumber":135,"sourceCode":"      if (error.message.includes('unexpected JWT alg received')) {\n        this.logger.warn(\n          [\n            'Algorithm mismatch. Make sure the signing algorithm is set correctly in the OAuth settings.',\n            'Or, that you have specified a signing key in your OAuth provider.',\n          ].join(' '),\n        );\n      }\n\n      this.logger.error('OAuth login failed', error);\n\n      throw new Error('OAuth login failed', { cause: error });\n    }\n  }\n\n  async getProfilePicture(url: string) {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to fetch picture: ${response.statusText}`);\n    }\n\n    return response.arrayBuffer();\n  }\n\n  private jwksClients: Map<string, JWTVerifyGetKey> = new Map(); // useful for caching and performnce\n  async validateLogoutToken(config: OAuthConfig, logoutToken: string): Promise<{ sub?: string; sid?: string } | null> {\n    const client = await this.getClient(config);\n    const algorithm = client.clientMetadata().id_token_signed_response_alg ?? 'RS256';\n    let keyOrGetter: Uint8Array | JWTVerifyGetKey;\n\n    try {\n      if (algorithm.startsWith('HS')) {\n        keyOrGetter = new TextEncoder().encode(config.clientSecret);\n      } else {\n        const jwksUri = client.serverMetadata().jwks_uri;\n        if (!jwksUri) {\n          throw new Error('Unable to get JWKS URI');","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/immich-app/immich/blob/5666d57f15a66bd5518119c5d9f4d2b62f3a86c1/server/src/repositories/oauth.repository.ts#L117-L153","documentation":"`getProfilePicture` fetches an avatar URL (usually from the OAuth provider) and throws `Failed to fetch picture: <statusText>` whenever the HTTP response is not ok (status outside 200-299). It is a plain fetch with no retry or auth, so any 403/404/5xx from the image host surfaces as this error.","triggerScenarios":"The profile image URL returned by the provider is expired (many providers' avatar URLs are signed and short-lived), returns 403 when fetched without credentials/UA, 404 after the user removed the picture, rate-limits (429), or the host is unreachable/DNS fails.","commonSituations":"Provider avatar URLs expiring between profile fetch and image download; hotlink protection on the image CDN blocking server-side fetches; self-hosted provider on an internal hostname the server can't resolve; large images hitting timeouts.","solutions":["Log the URL and statusText/status in the error to see why the fetch failed (403 vs 404 vs DNS).","Re-fetch the picture URL from a fresh profile response — signed URLs may have expired.","Check that the image host is reachable from the server (curl the URL from inside the container); fix DNS/proxy if self-hosted.","Add headers the CDN may require (User-Agent) or fetch the picture via the provider's API instead of the raw URL.","Treat as non-fatal: catch the error and fall back to a default avatar instead of failing the whole login."],"exampleFix":"// before\nconst pic = await getProfilePicture(profile.avatarUrl); // throws on 403\n// after\ntry {\n  pic = await getProfilePicture(profile.avatarUrl);\n} catch {\n  pic = DEFAULT_AVATAR;\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const buf = await getProfilePicture(url);\n} catch (e) {\n  // e.message contains the statusText\n  console.warn(`avatar fetch failed (${e.message}), using default`);\n  const buf = DEFAULT_AVATAR;\n}","preventionTips":["Treat avatar fetch as best-effort and fall back to a default image.","Re-fetch profile/avatar URLs fresh instead of caching signed URLs long-term.","Ensure the server can reach the image host (DNS, egress rules).","Send a User-Agent if the CDN blocks requests without one.","Handle 429 rate limits with backoff rather than immediate failure."],"tags":["http","network","fetch","avatar","oauth"],"backgroundTag":"http-fetch-failed","analyzedSha":"5666d57f15a66bd5518119c5d9f4d2b62f3a86c1","analyzedAt":"2026-09-01T05:20:49.208Z","contentChangedAt":"2026-09-01T05:20:49.208Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}