{"record":{"id":"9fa73763b48b284a","repo":"toeverything/AFFiNE","slug":"cannot-activate-a-missing-workspace-invitation","errorCode":null,"errorMessage":"Cannot activate a missing workspace invitation.","messagePattern":"Cannot activate a missing workspace invitation\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/backend/server/src/models/permission-write.ts","lineNumber":313,"sourceCode":"      status: invitationStatus,\n      kind: workspaceInvitationKindToNew(data.source),\n    });\n  }\n\n  @Transactional()\n  async setState(\n    workspaceId: string,\n    userId: string,\n    status: WorkspaceMemberStatus,\n    data: {\n      inviterId?: string;\n    } = {}\n  ) {\n    const invitationStatus = workspaceStatusToInvitationState(status);\n    if (!invitationStatus) {\n      const invitation = await this.findInvitation(workspaceId, userId);\n      if (!invitation) {\n        throw new Error('Cannot activate a missing workspace invitation.');\n      }\n      const role =\n        invitation.requestedRole === 'admin'\n          ? WorkspaceRole.Admin\n          : WorkspaceRole.Collaborator;\n      return await this.models.workspaceMember.setActive(\n        workspaceId,\n        userId,\n        role,\n        { source: invitation.source }\n      );\n    }\n\n    return await this.updateInvitationStatus({\n      workspaceId,\n      userId,\n      status: invitationStatus,\n      inviterId: data.inviterId,","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/models/permission-write.ts#L295-L331","documentation":"Thrown by WorkspaceInvitationModel.setState (permission-write.ts:313) when asked to move a user to a non-invitation (Accepted) state but no invitation row exists in workspace_invitations for that user. Without the invitation the model cannot derive the requested role (admin vs member) needed to activate the member, so it refuses. Plain Error.","triggerScenarios":"Calling setState(workspaceId, userId, Accepted) when findInvitation returns null — e.g. accepting an invitation that was already revoked/deleted, or accepting for a user who was added directly as an active member (so no invitation row exists).","commonSituations":"User clicks a stale/old invite link after it was revoked; a concurrent accept+cancel race deleted the invitation row; a migration left active members without corresponding invitation rows; replaying an already-consumed accept token.","solutions":["Re-issue the invitation and have the user accept the fresh link.","If the user should be a member regardless of the missing invitation, call workspaceMember.setActive directly with an explicit role.","Check findInvitation before transitioning to Accepted and branch accordingly."],"exampleFix":"// before\nawait models.workspaceInvitation.setState(wsId, userId, WorkspaceMemberStatus.Accepted);\n// after\nconst inv = await models.workspaceInvitation.findInvitation(wsId, userId);\nif (!inv) {\n  await models.workspaceMember.setActive(wsId, userId, WorkspaceRole.Collaborator);\n} else {\n  await models.workspaceInvitation.setState(wsId, userId, WorkspaceMemberStatus.Accepted);\n}","handlingStrategy":"validation","validationCode":"async function acceptOrFail(models, wsId: string, userId: string) {\n  const inv = await models.workspaceInvitation.findInvitation(wsId, userId);\n  if (!inv) {\n    // no invitation to accept -> activate directly with an explicit role\n    await models.workspaceMember.setActive(wsId, userId, WorkspaceRole.Collaborator);\n    return;\n  }\n  await models.workspaceInvitation.setState(wsId, userId, WorkspaceMemberStatus.Accepted);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await models.workspaceInvitation.setState(wsId, userId, WorkspaceMemberStatus.Accepted);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Cannot activate a missing workspace invitation.') {\n    // re-issue invitation or activate the member directly with a known role\n  } else throw e;\n}","preventionTips":["Check findInvitation before transitioning a user to Accepted.","Revoke stale invitation links promptly so old tokens can't be replayed.","For users added directly as active members, call setActive instead of setState(Accepted)."],"tags":["workspace","invitation","state-machine"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}