toeverything/AFFiNE · info · UserAvatarNotFound

user_avatar_not_found

user_avatar_not_found

Error message

User avatar not found.

What it means

Thrown by UserAvatarController.getAvatar() after storage.get(id) returns an empty body. The provider check passed (fs/assetpack) but no blob exists for the given id, so there is nothing to stream.

Source

Thrown at packages/backend/server/src/core/user/controller.ts:29

@Public()
@Controller('/api/avatars')
export class UserAvatarController {
  constructor(private readonly storage: AvatarStorage) {}

  @Get('/:id')
  async getAvatar(@Res() res: Response, @Param('id') id: string) {
    const provider = this.storage.config.storage.provider;
    if (!['assetpack', 'fs'].includes(provider)) {
      throw new ActionForbidden(
        'Only available when avatar storage provider is fs or assetpack.'
      );
    }

    const { body, metadata } = await this.storage.get(id);

    if (!body) {
      throw new UserAvatarNotFound();
    }

    // metadata should always exists if body is not null
    if (metadata) {
      res.setHeader('content-type', metadata.contentType);
      res.setHeader('last-modified', metadata.lastModified.toISOString());
      res.setHeader('content-length', metadata.contentLength);
    }
    applyAttachHeaders(res, {
      contentType: metadata?.contentType,
      filename: `${id}`,
    });

    body.pipe(res);
  }
}

View on GitHub (pinned to 26c515e050)

Solutions

  1. Default to a generated/fallback avatar on the client when this 404 is returned.
  2. Clear cached avatar URLs when the user removes their avatar.
  3. Verify the id being requested matches the storage key format `${userId}-avatar-${timestamp}`.

Example fix

// before
<img src={`/api/avatars/${id}`} />

// after — handle 404 with fallback
<img src={avatarUrl} onError={(e) => { e.currentTarget.src = fallbackAvatar(id); }} />
Defensive patterns

Strategy: fallback

Validate before calling

const exists = await checkAvatarExists(id);
if (!exists) return fallbackAvatar(id);

Type guard

function hasAvatar(user?: { avatarUrl?: string | null }): boolean {
  return Boolean(user?.avatarUrl);
}

Try / catch

try {
  await fetch('/api/avatars/' + id);
} catch (e) {
  if (e?.code === 'user_avatar_not_found') { setFallbackAvatar(); return; }
  throw e;
}

Prevention

When it happens

Trigger: GET /api/avatars/:id where the storage backend has no object for that id — avatar was never uploaded, was deleted, or the id is wrong.

Common situations: User never set an avatar but the client still requests one; avatar was removed (removeAvatar mutation) and the client cached the old URL; id mismatch after a storage migration.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/e36a80857c121da5. Report an issue: GitHub.