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

  1. On non-team workspaces, only call grantMember to transfer ownership (newRole === WorkspaceRole.Owner).
  2. Upgrade the workspace to a Team plan to enable fine-grained role changes.
  3. 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

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


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