{"record":{"id":"1dc2ed01df6cbb12","repo":"toeverything/AFFiNE","slug":"user-not-found-1dc2ed","errorCode":"user_not_found","errorMessage":"User not found.","messagePattern":"User not found\\.","errorType":"exception","errorClass":"UserNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/member.ts","lineNumber":497,"sourceCode":"    return true;\n  }\n\n  @Throttle('strict')\n  @Query(() => InvitationType, {\n    description: 'get workspace invitation info',\n  })\n  async getInviteInfo(\n    @CurrentUser() user: UserType,\n    @Args('inviteId') inviteId: string\n  ): Promise<InvitationType> {\n    const { workspaceId, inviteeUserId, isLink } =\n      await this.workspaceService.getInviteInfo(inviteId);\n\n    if (!isLink && user.id !== inviteeUserId) {\n      throw new InvitationAccountMismatch();\n    }\n\n    const workspace = await this.workspaceService.getWorkspaceInfo(workspaceId);\n    const owner = await this.models.workspaceUser.getOwner(workspaceId);\n\n    const inviteeId = inviteeUserId || user.id;\n    const invitee = await this.models.user.getWorkspaceUser(inviteeId);\n    if (!invitee) throw new UserNotFound();\n\n    let status: WorkspaceMemberStatus | undefined;\n    if (isLink) {\n      const invitation = await this.models.workspaceUser.get(\n        workspaceId,\n        inviteeId\n      );\n      status = invitation?.status;\n    } else {\n      const invitation = await this.models.workspaceUser.getById(inviteId);\n      status = invitation?.status;\n    }\n","sourceCodeStart":479,"sourceCodeEnd":515,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/core/workspaces/resolvers/member.ts#L479-L515","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Require sign-in before fetching invite info: route the user through authentication when no session exists, then retry getInviteInfo.","In the client, check for an authenticated session before rendering the invite page and show a 'sign in to view this invitation' state.","If the invite should be open to anyone, use an invite link (isLink) rather than a targeted email invitation."],"exampleFix":"// before\nconst info = await getInviteInfo(inviteId); // anonymous + targeted invite -> user_not_found\n\n// after\nif (!(await session.currentUser())) {\n  return redirect(`/sign-in?redirect_uri=${encodeURIComponent(location.pathname)}`);\n}\nconst info = await getInviteInfo(inviteId);","handlingStrategy":"validation","validationCode":"// require a session before fetching targeted invite info\nconst user = await session.currentUser();\nif (!user) {\n  redirect(`/sign-in?redirect_uri=${encodeURIComponent(currentPath)}`);\n}\nawait getInviteInfo(inviteId);","typeGuard":"function isUserNotFound(e: unknown): boolean {\n  const ext = (e as { extensions?: Record<string, unknown> })?.extensions;\n  return String(ext?.name ?? '').toLowerCase() === 'user_not_found';\n}","tryCatchPattern":"try {\n  await getInviteInfo(inviteId);\n} catch (e) {\n  if (isUserNotFound(e) && !currentUser) {\n    await signInThenReturn(); // retry after authentication\n  } else throw e;\n}","preventionTips":["Treat invite pages as authenticated routes when invitations are targeted.","Keep auth sessions alive on long-lived invite tabs (silent refresh)."],"tags":["affine","graphql","invite","authentication","not-found"],"backgroundTag":"authentication-required","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}