Tencent/WeKnora · info

tenant is already an admin

Error message

tenant is already an admin

What it means

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.

Source

Thrown at internal/application/service/organization.go:625

	return org.OwnerTenantID == tenantID
}

// generateInviteCode generates a random 16-character invite code
func generateInviteCode() string {
	bytes := make([]byte, 8)
	_, _ = rand.Read(bytes)
	return hex.EncodeToString(bytes)
}

// ----------------
// Join Requests
// ----------------

var (
	ErrPendingRequestExists    = errors.New("pending request already exists")
	ErrJoinRequestNotFound     = errors.New("join request not found")
	ErrCannotUpgradeToSameRole = errors.New("cannot request upgrade to same or lower role")
	ErrAlreadyAdmin            = errors.New("tenant is already an admin")
)

// SubmitJoinRequest submits a request for the caller's tenant to join an organization.
// Dedup is now per-tenant: any user from a tenant already with a pending join
// request is rejected (the same tenant can't queue two simultaneous joins).
func (s *organizationService) SubmitJoinRequest(ctx context.Context, orgID string, userID string, tenantID uint64, message string, requestedRole types.OrgMemberRole) (*types.OrganizationJoinRequest, error) {
	logger.Infof(ctx, "Tenant %d (rep user %s) submitting join request for organization %s", tenantID, userID, orgID)

	existing, err := s.orgRepo.GetPendingRequestByTenantAndType(ctx, orgID, tenantID, types.JoinRequestTypeJoin)
	if err == nil && existing != nil {
		return nil, ErrPendingRequestExists
	}

	org, err := s.orgRepo.GetByID(ctx, orgID)
	if err != nil {
		if errors.Is(err, repository.ErrOrganizationNotFound) {
			return nil, ErrOrgNotFound
		}

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Do not call RequestRoleUpgrade for tenants already at OrgRoleAdmin; refresh the member's role first.
  2. Treat errors.Is(err, ErrAlreadyAdmin) as an idempotent success in retry flows.
  3. Filter admin members out of any UI list offering role upgrades.

Example fix

// before
svc.RequestRoleUpgrade(ctx, orgID, userID, tenantID, types.OrgRoleAdmin)
// after
if member.Role != types.OrgRoleAdmin {
    svc.RequestRoleUpgrade(ctx, orgID, userID, tenantID, types.OrgRoleAdmin)
}
Defensive patterns

Strategy: validation

Validate before calling

if member.Role == types.OrgRoleAdmin {
    return nil // already admin; skip upgrade request
}

Type guard

func isAdmin(m *types.OrgMember) bool { return m != nil && m.Role == types.OrgRoleAdmin }

Try / catch

if errors.Is(err, organization.ErrAlreadyAdmin) {
    return nil // idempotent success
}

Prevention

When it happens

Trigger: Calling RequestRoleUpgrade for a tenant whose org membership role is already types.OrgRoleAdmin.

Common situations: 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.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/3e81139a24630785. Report an issue: GitHub.