toeverything/AFFiNE · error · Error
Cannot grant Owner role of a workspace to a user.
Error message
Cannot grant Owner role of a workspace to a user.
What it means
Plain Error thrown by WorkspaceMemberModel.setActive (packages/backend/server/src/models/permission-write.ts:188). setActive is the path for activating/inviting members, and it explicitly forbids granting WorkspaceRole.Owner — ownership is a transfer, not an activation. Any call with role === WorkspaceRole.Owner throws immediately before any invitation/member write. Because it is a plain Error, callers see internal_server_error unless they validate first.
Source
Thrown at packages/backend/server/src/models/permission-write.ts:188
create: {
workspaceId,
userId,
role: 'owner',
state: 'active',
source: 'legacy',
},
});
}
@Transactional()
async setActive(
workspaceId: string,
userId: string,
role: WorkspaceRole,
data: { source?: PermissionSource } = {}
) {
if (role === WorkspaceRole.Owner) {
throw new Error('Cannot grant Owner role of a workspace to a user.');
}
const invitation = await this.db.workspaceInvitation.findUnique({
where: {
workspaceId_inviteeUserId: {
workspaceId,
inviteeUserId: userId,
},
},
});
await this.db.workspaceInvitation.deleteMany({
where: { workspaceId, inviteeUserId: userId },
});
return await this.db.workspaceMember.upsert({
where: {
workspaceId_userId_state: {
workspaceId,View on GitHub (pinned to 26c515e050)
Solutions
- Route Owner grants through WorkspaceMemberModel.setOwner(workspaceId, userId, fallbackRole), not setActive.
- Special-case Owner in the UI/controller: if (role === WorkspaceRole.Owner) return setOwner(...).
- Filter Owner out of any bulk setActive loop.
- Add a type/unit test asserting setActive rejects Owner so regressions surface early.
Example fix
// before
await memberModel.setActive(workspaceId, userId, WorkspaceRole.Owner);
// after
if (role === WorkspaceRole.Owner) {
await memberModel.setOwner(workspaceId, userId, WorkspaceRole.Admin);
} else {
await memberModel.setActive(workspaceId, userId, role);
} Defensive patterns
Strategy: validation
Validate before calling
if (role === WorkspaceRole.Owner) {
throw new Error('Use WorkspaceMemberModel.setOwner to grant Owner, not setActive');
}
await memberModel.setActive(workspaceId, userId, role); Type guard
const isActivationRole = (r: WorkspaceRole): boolean => r !== WorkspaceRole.Owner;
Try / catch
try {
await memberModel.setActive(workspaceId, userId, role);
} catch (e) {
if (e instanceof Error && /Cannot grant Owner/i.test(e.message)) {
await memberModel.setOwner(workspaceId, userId, WorkspaceRole.Admin);
return;
}
throw e;
} Prevention
- Special-case Owner in the controller: route to setOwner, not setActive.
- Filter Owner out of bulk setActive loops.
- Add a unit test asserting setActive rejects Owner.
When it happens
Trigger: Calling setActive(workspaceId, userId, WorkspaceRole.Owner, data) — the very first statement in setActive is the equality check, so any Owner role input throws. This is an API-contract violation: callers must use setOwner to grant ownership.
Common situations: Permission matrix UI mapping 'Owner' through the same setActive call as other roles; bulk role import that includes Owner; client code that switches on role without special-casing Owner; refactor that merged the owner path into setActive by mistake.
Related errors
- New workspace owner must be an active member.
- owner_can_not_leave_workspace
- can_not_batch_grant_doc_owner_permissions
- failed_to_save_updates
- doc_history_not_found
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/88842f78c302f514.
Report an issue: GitHub.