toeverything/AFFiNE · error · UserNotFound

user_not_found

user_not_found

Error message

User not found.

What it means

Thrown by the getInviteInfo query when there is no invitee user id to render: the invite record has no inviteeUserId (or it is empty) and the caller is not signed in, so inviteeId = inviteeUserId || user?.id resolves to undefined. This is the anonymous-visitor-on-a-targeted-invite case. Code user_not_found, type resource_not_found, HTTP 404.

Solutions

  1. Require sign-in before fetching invite info: route the user through authentication when no session exists, then retry getInviteInfo.
  2. In the client, check for an authenticated session before rendering the invite page and show a 'sign in to view this invitation' state.
  3. If the invite should be open to anyone, use an invite link (isLink) rather than a targeted email invitation.

Example fix

// before
const info = await getInviteInfo(inviteId); // anonymous + targeted invite -> user_not_found

// after
if (!(await session.currentUser())) {
  return redirect(`/sign-in?redirect_uri=${encodeURIComponent(location.pathname)}`);
}
const info = await getInviteInfo(inviteId);
Defensive patterns

Strategy: validation

Validate before calling

// require a session before fetching targeted invite info
const user = await session.currentUser();
if (!user) {
  redirect(`/sign-in?redirect_uri=${encodeURIComponent(currentPath)}`);
}
await getInviteInfo(inviteId);

Type guard

function isUserNotFound(e: unknown): boolean {
  const ext = (e as { extensions?: Record<string, unknown> })?.extensions;
  return String(ext?.name ?? '').toLowerCase() === 'user_not_found';
}

Try / catch

try {
  await getInviteInfo(inviteId);
} catch (e) {
  if (isUserNotFound(e) && !currentUser) {
    await signInThenReturn(); // retry after authentication
  } else throw e;
}

Prevention

When it happens

Trigger: Opening an invite page (query getInviteInfo(inviteId)) where the invitation was created for a specific user, but the request is unauthenticated (no current user) — e.g. the recipient's link opened in a logged-out browser or incognito window, or an expired auth session dropped the CurrentUser.

Common situations: Invite preview pages rendered server-side or in incognito; auth token missing/expired so @CurrentUser is undefined; invitation links shared with a different person than the targeted invitee.

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/1dc2ed01df6cbb12. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/workspaces/resolvers/member.ts:497

    return true;
  }

  @Throttle('strict')
  @Query(() => InvitationType, {
    description: 'get workspace invitation info',
  })
  async getInviteInfo(
    @CurrentUser() user: UserType,
    @Args('inviteId') inviteId: string
  ): Promise<InvitationType> {
    const { workspaceId, inviteeUserId, isLink } =
      await this.workspaceService.getInviteInfo(inviteId);

    if (!isLink && user.id !== inviteeUserId) {
      throw new InvitationAccountMismatch();
    }

    const workspace = await this.workspaceService.getWorkspaceInfo(workspaceId);
    const owner = await this.models.workspaceUser.getOwner(workspaceId);

    const inviteeId = inviteeUserId || user.id;
    const invitee = await this.models.user.getWorkspaceUser(inviteeId);
    if (!invitee) throw new UserNotFound();

    let status: WorkspaceMemberStatus | undefined;
    if (isLink) {
      const invitation = await this.models.workspaceUser.get(
        workspaceId,
        inviteeId
      );
      status = invitation?.status;
    } else {
      const invitation = await this.models.workspaceUser.getById(inviteId);
      status = invitation?.status;
    }

View on GitHub (pinned to 2af30773ae)