toeverything/AFFiNE · error · Error

Cannot activate a missing workspace invitation.

Error message

Cannot activate a missing workspace invitation.

What it means

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.

Source

Thrown at packages/backend/server/src/models/permission-write.ts:313

      status: invitationStatus,
      kind: workspaceInvitationKindToNew(data.source),
    });
  }

  @Transactional()
  async setState(
    workspaceId: string,
    userId: string,
    status: WorkspaceMemberStatus,
    data: {
      inviterId?: string;
    } = {}
  ) {
    const invitationStatus = workspaceStatusToInvitationState(status);
    if (!invitationStatus) {
      const invitation = await this.findInvitation(workspaceId, userId);
      if (!invitation) {
        throw new Error('Cannot activate a missing workspace invitation.');
      }
      const role =
        invitation.requestedRole === 'admin'
          ? WorkspaceRole.Admin
          : WorkspaceRole.Collaborator;
      return await this.models.workspaceMember.setActive(
        workspaceId,
        userId,
        role,
        { source: invitation.source }
      );
    }

    return await this.updateInvitationStatus({
      workspaceId,
      userId,
      status: invitationStatus,
      inviterId: data.inviterId,

View on GitHub (pinned to 26c515e050)

Solutions

  1. Re-issue the invitation and have the user accept the fresh link.
  2. If the user should be a member regardless of the missing invitation, call workspaceMember.setActive directly with an explicit role.
  3. Check findInvitation before transitioning to Accepted and branch accordingly.

Example fix

// before
await models.workspaceInvitation.setState(wsId, userId, WorkspaceMemberStatus.Accepted);
// after
const inv = await models.workspaceInvitation.findInvitation(wsId, userId);
if (!inv) {
  await models.workspaceMember.setActive(wsId, userId, WorkspaceRole.Collaborator);
} else {
  await models.workspaceInvitation.setState(wsId, userId, WorkspaceMemberStatus.Accepted);
}
Defensive patterns

Strategy: validation

Validate before calling

async function acceptOrFail(models, wsId: string, userId: string) {
  const inv = await models.workspaceInvitation.findInvitation(wsId, userId);
  if (!inv) {
    // no invitation to accept -> activate directly with an explicit role
    await models.workspaceMember.setActive(wsId, userId, WorkspaceRole.Collaborator);
    return;
  }
  await models.workspaceInvitation.setState(wsId, userId, WorkspaceMemberStatus.Accepted);
}

Try / catch

try {
  await models.workspaceInvitation.setState(wsId, userId, WorkspaceMemberStatus.Accepted);
} catch (e) {
  if (e instanceof Error && e.message === 'Cannot activate a missing workspace invitation.') {
    // re-issue invitation or activate the member directly with a known role
  } else throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/9fa73763b48b284a. Report an issue: GitHub.