toeverything/AFFiNE · error · ActionForbiddenOnNonTeamWorkspace
action_forbidden_on_non_team_workspace
action_forbidden_on_non_team_workspace
Error message
A Team workspace is required to perform this action.
What it means
Thrown by grantMember when changing to a non-Owner role on a workspace that is not a Team workspace. Non-team workspaces only support transferring ownership; fine-grained per-member roles require a Team plan/workspace. The check runs after the membership lookup, only on the non-Owner branch. Reported as action_forbidden_on_non_team_workspace (action_forbidden, HTTP 403).
Source
Thrown at packages/backend/server/src/core/workspaces/resolvers/member.ts:591
.assert(
newRole === WorkspaceRole.Owner
? 'Workspace.TransferOwner'
: 'Workspace.Users.Manage'
);
const role = await this.models.workspaceUser.get(workspaceId, userId);
if (!role) {
throw new MemberNotFoundInSpace({ spaceId: workspaceId });
}
if (newRole === WorkspaceRole.Owner) {
await this.models.workspaceUser.setOwner(workspaceId, userId);
} else {
// non-team workspace can only transfer ownership, but no detailed permission control
const isTeam = await this.workspaceService.isTeamWorkspace(workspaceId);
if (!isTeam) {
throw new ActionForbiddenOnNonTeamWorkspace();
}
await this.models.workspaceUser.set(workspaceId, userId, newRole);
if (role.status !== WorkspaceMemberStatus.Accepted) {
this.event.emit('workspace.members.updated', {
workspaceId,
});
}
}
return true;
}
@Throttle('strict')
@Public()
@Query(() => InvitationType, {
description: 'get workspace invitation info',
})View on GitHub (pinned to 26c515e050)
Solutions
- On non-team workspaces, only call grantMember to transfer ownership (newRole === WorkspaceRole.Owner).
- Upgrade the workspace to a Team plan to enable fine-grained role changes.
- Hide non-owner role controls in the UI when isTeamWorkspace is false.
Example fix
// before (non-team workspace) grantMember(workspaceId, userId, WorkspaceRole.Collaborator); // after if (isTeamWorkspace) grantMember(workspaceId, userId, WorkspaceRole.Collaborator); else grantMember(workspaceId, userId, WorkspaceRole.Owner); // transfer only
Defensive patterns
Strategy: validation
Validate before calling
const isTeam = await isTeamWorkspace(workspaceId);
if (!isTeam && newRole !== WorkspaceRole.Owner) {
throw new Error('Non-team workspaces only support transferring ownership');
} Type guard
function canGrantRole(isTeam: boolean, role: WorkspaceRole): boolean {
return isTeam || role === WorkspaceRole.Owner;
} Prevention
- Hide non-owner role controls on non-team workspaces.
- Upgrade to a Team plan to enable fine-grained roles.
- On non-team workspaces, only call grantMember to transfer ownership.
When it happens
Trigger: Calling grantMember with newRole != WorkspaceRole.Owner on a workspace where workspaceService.isTeamWorkspace returns false.
Common situations: A role-management UI is shown on a non-team (free/personal) workspace; the client assumes all workspaces support arbitrary roles; attempting to demote a member on a basic workspace.
Related errors
- expect_to_revoke_doc_user_roles
- expect_to_update_doc_user_role
- expect_to_grant_doc_user_roles
- doc_default_role_can_not_be_owner
- action_forbidden
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/1003b65c9fcd1fc1.
Report an issue: GitHub.