{"record":{"id":"3e81139a24630785","repo":"Tencent/WeKnora","slug":"tenant-is-already-an-admin","errorCode":null,"errorMessage":"tenant is already an admin","messagePattern":"tenant is already an admin","errorType":"http","errorClass":null,"httpStatus":409,"severity":"info","filePath":"internal/application/service/organization.go","lineNumber":625,"sourceCode":"\treturn org.OwnerTenantID == tenantID\n}\n\n// generateInviteCode generates a random 16-character invite code\nfunc generateInviteCode() string {\n\tbytes := make([]byte, 8)\n\t_, _ = rand.Read(bytes)\n\treturn hex.EncodeToString(bytes)\n}\n\n// ----------------\n// Join Requests\n// ----------------\n\nvar (\n\tErrPendingRequestExists    = errors.New(\"pending request already exists\")\n\tErrJoinRequestNotFound     = errors.New(\"join request not found\")\n\tErrCannotUpgradeToSameRole = errors.New(\"cannot request upgrade to same or lower role\")\n\tErrAlreadyAdmin            = errors.New(\"tenant is already an admin\")\n)\n\n// SubmitJoinRequest submits a request for the caller's tenant to join an organization.\n// Dedup is now per-tenant: any user from a tenant already with a pending join\n// request is rejected (the same tenant can't queue two simultaneous joins).\nfunc (s *organizationService) SubmitJoinRequest(ctx context.Context, orgID string, userID string, tenantID uint64, message string, requestedRole types.OrgMemberRole) (*types.OrganizationJoinRequest, error) {\n\tlogger.Infof(ctx, \"Tenant %d (rep user %s) submitting join request for organization %s\", tenantID, userID, orgID)\n\n\texisting, err := s.orgRepo.GetPendingRequestByTenantAndType(ctx, orgID, tenantID, types.JoinRequestTypeJoin)\n\tif err == nil && existing != nil {\n\t\treturn nil, ErrPendingRequestExists\n\t}\n\n\torg, err := s.orgRepo.GetByID(ctx, orgID)\n\tif err != nil {\n\t\tif errors.Is(err, repository.ErrOrganizationNotFound) {\n\t\t\treturn nil, ErrOrgNotFound\n\t\t}","sourceCodeStart":607,"sourceCodeEnd":643,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/organization.go#L607-L643","documentation":"ErrAlreadyAdmin is a sentinel returned by RequestRoleUpgrade when the tenant's current role is already OrgRoleAdmin. Admins cannot request an upgrade because no higher role exists; this is a fast-fail before the role-comparison check.","triggerScenarios":"Calling RequestRoleUpgrade for a tenant whose org membership role is already types.OrgRoleAdmin.","commonSituations":"Stale client state after a previous upgrade was approved, admins re-clicking an upgrade button, or tenants listed in an upgrade queue that was not refreshed post-approval.","solutions":["Do not call RequestRoleUpgrade for tenants already at OrgRoleAdmin; refresh the member's role first.","Treat errors.Is(err, ErrAlreadyAdmin) as an idempotent success in retry flows.","Filter admin members out of any UI list offering role upgrades."],"exampleFix":"// before\nsvc.RequestRoleUpgrade(ctx, orgID, userID, tenantID, types.OrgRoleAdmin)\n// after\nif member.Role != types.OrgRoleAdmin {\n    svc.RequestRoleUpgrade(ctx, orgID, userID, tenantID, types.OrgRoleAdmin)\n}","handlingStrategy":"validation","validationCode":"if member.Role == types.OrgRoleAdmin {\n    return nil // already admin; skip upgrade request\n}","typeGuard":"func isAdmin(m *types.OrgMember) bool { return m != nil && m.Role == types.OrgRoleAdmin }","tryCatchPattern":"if errors.Is(err, organization.ErrAlreadyAdmin) {\n    return nil // idempotent success\n}","preventionTips":["Refresh the member's role after any approved upgrade before retrying.","Filter admin tenants out of upgrade-eligible lists.","Treat ErrAlreadyAdmin as success in retry flows."],"tags":["role-permissions","organization","go","idempotency"],"backgroundTag":"already-in-target-state","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}