{"record":{"id":"87a95af2d91fa96f","repo":"n8n-io/n8n","slug":"instance-roles-are-managed-automatically-and-canno","errorCode":null,"errorMessage":"Instance roles are managed automatically and cannot be changed manually","messagePattern":"Instance roles are managed automatically and cannot be changed manually","errorType":"exception","errorClass":"ForbiddenError","httpStatus":403,"severity":"error","filePath":"packages/cli/src/controllers/users.controller.ts","lineNumber":348,"sourceCode":"\t\t\tmigrationUserId: transfereeId,\n\t\t});\n\n\t\tawait this.externalHooks.run('user.deleted', [await this.userService.toPublic(userToDelete)]);\n\n\t\treturn { success: true };\n\t}\n\n\t@Patch('/:id/role')\n\t@GlobalScope('user:changeRole')\n\t@Licensed('feat:advancedPermissions')\n\tasync changeGlobalRole(\n\t\treq: AuthenticatedRequest,\n\t\t_: Response,\n\t\t@Body payload: RoleChangeRequestDto,\n\t\t@Param('id') id: string,\n\t) {\n\t\tif (await this.provisioningService.isInstanceRoleManaged()) {\n\t\t\tthrow new ForbiddenError(\n\t\t\t\t'Instance roles are managed automatically and cannot be changed manually',\n\t\t\t);\n\t\t}\n\n\t\tconst { NO_ADMIN_ON_OWNER, NO_USER, NO_OWNER_ON_OWNER, CANNOT_CHANGE_OWN_ROLE } =\n\t\t\tUsersController.ERROR_MESSAGES.CHANGE_ROLE;\n\n\t\tif (req.user.id === id) {\n\t\t\tthrow new ForbiddenError(CANNOT_CHANGE_OWN_ROLE);\n\t\t}\n\n\t\tconst targetUser = await this.userRepository.findOne({\n\t\t\twhere: { id },\n\t\t\trelations: ['role'],\n\t\t});\n\t\tif (targetUser === null) {\n\t\t\tthrow new NotFoundError(NO_USER);\n\t\t}","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/cli/src/controllers/users.controller.ts#L330-L366","documentation":"Returned by PATCH /users/:id/role (scope user:changeRole, license feat:advancedPermissions) when provisioningService.isInstanceRoleManaged() resolves true — meaning role assignment is driven by an external provisioning system (SAML/LDAP/SCIM). In that mode, manual role changes from the UI/API are forbidden to avoid drift. HTTP 403.","triggerScenarios":"Any PATCH /users/:id/role call while the instance is configured for externally-managed roles (SAML JIT, LDAP sync, SCIM). The check runs before any per-user logic.","commonSituations":"Enterprise deployment with SAML/SCIM where directory groups map to n8n roles; an admin tries the legacy 'change role' button that the UI failed to hide when managed mode is on.","solutions":["Change the user's role in the upstream identity provider / directory group instead of via this endpoint.","If manual changes are intentionally allowed, disable externally-managed roles in instance config (consult the access-management runbook first).","Have the UI hide the role-change control when isInstanceRoleManaged() is true."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"async function isInstanceRoleManaged() {\n  const r = await fetch('/rest/settings', { headers: authHeaders() });\n  const s = await r.json();\n  return Boolean(s.data?.externalIdentityProviderEnabled || s.data?.samlEnabled || s.data?.ldapEnabled);\n}\nif (await isInstanceRoleManaged()) {\n  throw new Error('Roles are managed by the identity provider; change them there');\n}","typeGuard":null,"tryCatchPattern":"try { await fetch(`/rest/users/${id}/role`, { method: 'PATCH', body: JSON.stringify(payload) }); }\ncatch (e) { if (e.statusCode === 403 && /managed automatically/.test(e.message)) { /* route to IdP */ } else throw e; }","preventionTips":["When SAML/SCIM/LDAP is enabled, change roles in the directory, not the API.","Have the UI hide the role-change control when isInstanceRoleManaged() is true.","Document the externally-managed mode for operators."],"tags":["users","rbac","saml","scim","ldap","provisioning","forbidden","authorization"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}