{"record":{"id":"b6e30dfccdf49c16","repo":"plandex-ai/plandex","slug":"cannot-delete-the-only-org-owner","errorCode":null,"errorMessage":"Cannot delete the only org owner","messagePattern":"Cannot delete the only org owner","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"app/server/handlers/users.go","lineNumber":183,"sourceCode":"\tif err != nil {\n\t\tlog.Printf(\"Error getting org owner role id: %v\\n\", err)\n\t\thttp.Error(w, \"Error getting org owner role id: \"+err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// verify user isn't the only org owner\n\tif orgUser.OrgRoleId == orgOwnerRoleId {\n\t\tnumOwners, err := db.NumUsersWithRole(auth.OrgId, orgOwnerRoleId)\n\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error getting number of org owners: %v\\n\", err)\n\t\t\thttp.Error(w, \"Error getting number of org owners: \"+err.Error(), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tif numOwners == 1 {\n\t\t\tlog.Println(\"Cannot delete the only org owner\")\n\t\t\thttp.Error(w, \"Cannot delete the only org owner\", http.StatusForbidden)\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = db.WithTx(r.Context(), \"delete org user\", func(tx *sqlx.Tx) error {\n\n\t\terr = db.DeleteOrgUser(auth.OrgId, userId, tx)\n\n\t\tif err != nil {\n\t\t\tlog.Println(\"Error deleting org user: \", err)\n\t\t\treturn fmt.Errorf(\"error deleting org user: %v\", err)\n\t\t}\n\n\t\tinvite, err := db.GetActiveInviteByEmail(auth.OrgId, auth.User.Email)\n\n\t\tif err != nil {\n\t\t\tlog.Println(\"Error getting invite for org user: \", err)\n\t\t\treturn fmt.Errorf(\"error getting invite for org user: %v\", err)","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/handlers/users.go#L165-L201","documentation":"A deliberate guard: the target user is an org owner and numOwners == 1, so deleting them would leave the org without any owner. The handler rejects with HTTP 403 'Cannot delete the only org owner'. This protects orgs from lockout of administrative actions.","triggerScenarios":"DELETE request for the sole user holding the org owner role in the org — the only remaining owner is targeted for removal.","commonSituations":"Downsizing a team where the founder is the last owner; scripted cleanup scripts deleting users indiscriminately; owner trying to delete themselves when no co-owner exists.","solutions":["Promote another member to the owner role first, then delete the target owner","If the caller is the last owner removing themselves, transfer ownership instead","Adjust automation scripts to skip or handle users whose org_role_id equals the owner role id","If the org should be decommissioned, delete the org itself rather than its last owner"],"exampleFix":"// before\nDELETE /org-users/user-123  // user-123 is the only owner -> 403\n// after\nPOST /org-users/owner-transfer {\"newOwnerId\": \"user-456\"}\nDELETE /org-users/user-123  // now succeeds","handlingStrategy":"validation","validationCode":"var ownerCount int\ndb.Get(&ownerCount,\n  \"SELECT COUNT(*) FROM org_users WHERE org_id=$1 AND org_role_id=$2\",\n  orgId, ownerRoleId)\nif targetRoleId == ownerRoleId && ownerCount <= 1 {\n    return errors.New(\"cannot remove the only org owner; transfer ownership first\")\n}","typeGuard":"func isSoleOwner(db *sqlx.DB, userId, orgId, ownerRoleId string) bool {\n    var role string\n    var n int\n    _ = db.Get(&role, \"SELECT org_role_id FROM org_users WHERE user_id=$1 AND org_id=$2\", userId, orgId)\n    if role != ownerRoleId { return false }\n    _ = db.Get(&n, \"SELECT COUNT(*) FROM org_users WHERE org_id=$1 AND org_role_id=$2\", orgId, ownerRoleId)\n    return n <= 1\n}","tryCatchPattern":"if resp.StatusCode == http.StatusForbidden &&\n   strings.Contains(body, \"Cannot delete the only org owner\") {\n    // prompt user to promote a co-owner first, do not retry\n    return errLastOwner\n}","preventionTips":["Before removing an owner, promote at least one other member to owner role","Handle self-removal of the last owner via an explicit ownership-transfer flow","Make bulk cleanup scripts query org_role_id and skip sole owners","Surface this 403 distinctly in UIs with a 'transfer ownership first' prompt"],"tags":["go","authorization","http-403","org-owner","business-rule"],"backgroundTag":"last-owner-delete-forbidden","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}