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
- Sign in and retry the upload with the session attached
- Ensure your GraphQL client sends credentials (cookies/authorization) on multipart avatar uploads
- 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
- Send credentials on multipart GraphQL uploads (cookies/authorization header)
- Check auth state before enabling the avatar upload control
- Interpret USER_NOT_FOUND from account mutations as an expired session signal
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)