{"record":{"id":"a8c1b492dcbd1c17","repo":"semaphoreui/semaphore","slug":"owner-can-not-change-his-role-in-the-project","errorCode":null,"errorMessage":"owner can not change his role in the project","messagePattern":"owner can not change his role in the project","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"api/projects/users.go","lineNumber":167,"sourceCode":"func LeftProject(w http.ResponseWriter, r *http.Request) {\n\tme := helpers.GetFromContext(r, \"user\").(*db.User) // logged in user\n\tremoveUser(*me, w, r)\n}\n\n// RemoveUser removes a user from a project team\nfunc RemoveUser(w http.ResponseWriter, r *http.Request) {\n\ttargetUser := helpers.GetFromContext(r, \"projectUser\").(db.User) // target user\n\tremoveUser(targetUser, w, r)\n}\n\nfunc UpdateUser(w http.ResponseWriter, r *http.Request) {\n\tproject := helpers.GetFromContext(r, \"project\").(db.Project)\n\tme := helpers.GetFromContext(r, \"user\").(*db.User) // logged in user\n\ttargetUser := helpers.GetFromContext(r, \"projectUser\").(db.User)\n\ttargetUserRole := helpers.GetFromContext(r, \"projectUserRole\").(db.ProjectUserRole)\n\n\tif !me.Admin && targetUser.ID == me.ID && targetUserRole == db.ProjectOwner {\n\t\thelpers.WriteError(w, fmt.Errorf(\"owner can not change his role in the project\"))\n\t\treturn\n\t}\n\n\tvar projectUser struct {\n\t\tRole db.ProjectUserRole `json:\"role\"`\n\t}\n\n\tif !helpers.Bind(w, r, &projectUser) {\n\t\treturn\n\t}\n\n\tif !projectUser.Role.IsValid() {\n\t\t_, err := helpers.Store(r).GetProjectOrGlobalRoleBySlug(project.ID, string(projectUser.Role))\n\t\tif err != nil {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t}","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/api/projects/users.go#L149-L185","documentation":"UpdateUser (api/projects/users.go:167) prevents a non-admin project owner from changing their own role: if !me.Admin && targetUser.ID == me.ID && targetUserRole == db.ProjectOwner it returns 'owner can not change his role in the project'. This stops the last owner from demoting themselves and orphaning the project.","triggerScenarios":"PUT /api/project/{project_id}/users/{user_id} (role change) where the authenticated user targets themselves, is not an admin, and currently holds db.ProjectOwner.","commonSituations":"Owner trying to demote themselves to member/maintainer directly; UI role editor saving the owner's own row; automation that iterates members and rewrites roles including the owner.","solutions":["Have another project owner (or an instance admin) change your role.","First promote another member to owner, then have that owner demote you.","Log in as an admin account to perform the role change — admins bypass the guard.","If no second owner exists, promote one via that owner's session or admin API before self-demotion."],"exampleFix":"// before\nPUT /api/project/42/users/7 {\"role\": \"member\"}   // as owner #7 -> rejected\n// after (as admin, or another owner performs it)\nPUT /api/project/42/users/7 {\"role\": \"member\"}","handlingStrategy":"type-guard","validationCode":"// before PUT role change\nif (target.id === me.id && me.role === \"owner\" && !me.admin) {\n  throw new Error(\"Owners cannot change their own role; ask another owner or an admin\");\n}","typeGuard":"function isSelfRoleChangeBlocked(me, targetUser, targetUserRole) {\n  return !me.admin && targetUser.id === me.id && targetUserRole === \"owner\";\n}","tryCatchPattern":"try {\n  await updateProjectUserRole(projectId, userId, role);\n} catch (e) {\n  if (/owner can not change his role/.test(e.message)) {\n    show(\"Ask another project owner or an admin to change your role.\");\n  }\n}","preventionTips":["Hide the role editor for a user's own owner row in the UI","Maintain at least two owners per project to allow role changes","Perform self-demotions through an admin","Include this guard in API client tests before bulk role rewrites"],"tags":["projects","authorization","roles"],"backgroundTag":"permission-denied","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}