toeverything/AFFiNE · error · UserNotFound

user_not_found

user_not_found

Error message

User not found.

What it means

The uploadAvatar GraphQL mutation requires a current user; when the request is unauthenticated (or the session expired so @CurrentUser resolves to null) it throws user_not_found rather than an authentication error.

Solutions

  1. Sign in and retry the upload with the session attached
  2. Ensure your GraphQL client sends credentials (cookies/authorization) on multipart avatar uploads
  3. Handle USER_NOT_FOUND from this mutation as a prompt to re-authenticate
Defensive patterns

Strategy: validation

Validate before calling

// ensure a session before uploading
if (!session.user) {
  await redirectToSignIn();
  return;
}
await uploadAvatar(file);

Type guard

function isCurrentUser(user?: CurrentUser | null): user is CurrentUser {
  return !!user?.id;
}

Try / catch

try {
  await uploadAvatar(file);
} catch (e) {
  if (e?.extensions?.code === 'USER_NOT_FOUND') await reauthenticateAndRetry();
  else throw e;
}

Prevention

When it happens

Trigger: Calling the uploadAvatar mutation without a session cookie/access token; an expired session at upload time; GraphQL clients dropping the auth header on multipart uploads.

Common situations: Avatar upload attempted after token expiry; multipart GraphQL proxying that strips cookies; scripts calling the mutation directly without credentials.

Understand the failure class

Background: "User not found", "Invalid user", and "does not exist": what missing-user lookup errors mean across Rocket.Chat, LiteLLM, Phabricator, rustfs, and pnpm — this error's family across 10 libraries.

Related errors


AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18). Data as JSON: /api/errors/746d10729c52550f. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/user/resolver.ts:121

  })
  @Public()
  async getPublicUserById(
    @Args('id', { type: () => String }) id: string
  ): Promise<PublicUserType | null> {
    return await this.models.user.getPublicUser(id);
  }

  @Mutation(() => UserType, {
    name: 'uploadAvatar',
    description: 'Upload user avatar',
  })
  async uploadAvatar(
    @CurrentUser() user: CurrentUser,
    @Args({ name: 'avatar', type: () => GraphQLUpload })
    avatar: FileUpload
  ) {
    if (!user) {
      throw new UserNotFound();
    }

    const avatarBuffer = await readBufferWithLimit(
      avatar.createReadStream(),
      5 * OneMB
    );
    const contentType = sniffMime(avatarBuffer, avatar.mimetype)?.toLowerCase();
    if (!contentType || !contentType.startsWith('image/')) {
      throw new ImageFormatNotSupported({ format: contentType || 'unknown' });
    }

    let processedAvatarBuffer: Buffer;
    try {
      processedAvatarBuffer = await processImage(avatarBuffer, 512, false);
    } catch {
      throw new ImageFormatNotSupported({ format: contentType });
    }

View on GitHub (pinned to 2af30773ae)