immich-app/immich · error · Error
Failed to fetch picture: ${response.statusText}
Error message
Failed to fetch picture: ${response.statusText} What it means
`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.
Source
Thrown at server/src/repositories/oauth.repository.ts:135
if (error.message.includes('unexpected JWT alg received')) {
this.logger.warn(
[
'Algorithm mismatch. Make sure the signing algorithm is set correctly in the OAuth settings.',
'Or, that you have specified a signing key in your OAuth provider.',
].join(' '),
);
}
this.logger.error('OAuth login failed', error);
throw new Error('OAuth login failed', { cause: error });
}
}
async getProfilePicture(url: string) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch picture: ${response.statusText}`);
}
return response.arrayBuffer();
}
private jwksClients: Map<string, JWTVerifyGetKey> = new Map(); // useful for caching and performnce
async validateLogoutToken(config: OAuthConfig, logoutToken: string): Promise<{ sub?: string; sid?: string } | null> {
const client = await this.getClient(config);
const algorithm = client.clientMetadata().id_token_signed_response_alg ?? 'RS256';
let keyOrGetter: Uint8Array | JWTVerifyGetKey;
try {
if (algorithm.startsWith('HS')) {
keyOrGetter = new TextEncoder().encode(config.clientSecret);
} else {
const jwksUri = client.serverMetadata().jwks_uri;
if (!jwksUri) {
throw new Error('Unable to get JWKS URI');View on GitHub (pinned to 5666d57f15)
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.
Example fix
// before
const pic = await getProfilePicture(profile.avatarUrl); // throws on 403
// after
try {
pic = await getProfilePicture(profile.avatarUrl);
} catch {
pic = DEFAULT_AVATAR;
} Defensive patterns
Strategy: fallback
Try / catch
try {
const buf = await getProfilePicture(url);
} catch (e) {
// e.message contains the statusText
console.warn(`avatar fetch failed (${e.message}), using default`);
const buf = DEFAULT_AVATAR;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Expected a JSON response
- OAuth login failed
- Error in OAuth discovery: ${error}
- The route ${request.path} was requested as ${request.header(
- password is required
AI-assisted analysis of immich-app/immich@5666d57f15 (2026-09-01).
Data as JSON: /api/errors/5e1c4cd992d7284c.
Report an issue: GitHub.