Db:seed_fu échoue sur 002_groups.rb lors de la mise à niveau : Validation échouée : Nom déjà pris

J’ai rencontré un problème lors de la mise à niveau depuis v2026.3.0-latest. La tâche rake db:seed_fu échoue lorsqu’elle atteint 002_groups.rb avec l’erreur suivante :

ActiveRecord::RecordInvalid: Validation failed: Name has already been taken. (ActiveRecord::RecordInvalid)

Le plantage se produit exactement lorsque le seed tente d’initialiser les nouveaux groupes système (ID 4 : anonymous et ID 5 : logged_in_users).

J’ai confirmé cela via la console Rails. Une vérification manuelle échoue lors de la validation, mais contourner la validation permet d’insérer l’enregistrement dans la base de données sans aucun problème :

# Cela échoue avec « Name has already been taken »
g = Group.new(id: 4, name: "anonymous", automatic: true)
g.valid?

# Cela fonctionne, prouvant qu'il n'y a pas de conflit réel
g.save(validate: false)

J’ai réussi à surmonter cet obstacle en créant manuellement ces nouveaux groupes système :

ActiveRecord::Base.transaction do
  g4 = Group.new(id: 4, name: "anonymous", automatic: true)
  g4.save(validate: false)

  g5 = Group.new(id: 5, name: "logged_in_users", automatic: true)
  g5.save(validate: false)
end

Désormais, lorsque j’exécute rake db:seed_fu, la tâche se termine sans erreur.

Pile d’appels complète :

# rake --trace db:seed_fu
** Invoke db:seed_fu (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute db:seed_fu

== Seed from /var/www/discourse/db/fixtures/001_refresh.rb

== Seed from /var/www/discourse/db/fixtures/002_groups.rb
rake aborted!
ActiveRecord::RecordInvalid: Validation failed: Name has already been taken (ActiveRecord::RecordInvalid)
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/validations.rb:87:in 'ActiveRecord::Validations#raise_validation_error'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/validations.rb:54:in 'ActiveRecord::Validations#save!'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:365:in 'block in ActiveRecord::Transactions#save!'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:417:in 'block (2 levels) in ActiveRecord::Transactions#with_transaction_returning_status'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:357:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:413:in 'block in ActiveRecord::Transactions#with_transaction_returning_status'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/connection_pool.rb:416:in 'ActiveRecord::ConnectionAdapters::ConnectionPool#with_connection'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_handling.rb:312:in 'ActiveRecord::ConnectionHandling#with_connection'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:409:in 'ActiveRecord::Transactions#with_transaction_returning_status'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:365:in 'ActiveRecord::Transactions#save!'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/suppressor.rb:56:in 'ActiveRecord::Suppressor#save!'
/var/www/discourse/app/models/group.rb:539:in 'Group.refresh_automatic_group!'
/var/www/discourse/app/models/group.rb:719:in 'block in Group.ensure_automatic_groups!'
/var/www/discourse/app/models/group.rb:719:in 'Hash#each_key'
/var/www/discourse/app/models/group.rb:719:in 'Group.ensure_automatic_groups!'
(eval at /var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:46):3:in 'block (2 levels) in SeedFu::Runner#run_file'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:46:in 'Kernel#eval'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:46:in 'block (2 levels) in SeedFu::Runner#run_file'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:58:in 'block in SeedFu::Runner#open'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:57:in 'IO.open'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:57:in 'SeedFu::Runner#open'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:36:in 'block in SeedFu::Runner#run_file'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/transaction.rb:626:in 'block in ActiveRecord::ConnectionAdapters::TransactionManager#within_new_transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activesupport-8.0.5/lib/active_support/concurrency/null_lock.rb:9:in 'ActiveSupport::Concurrency::NullLock#synchronize'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/transaction.rb:623:in 'ActiveRecord::ConnectionAdapters::TransactionManager#within_new_transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#within_new_transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:359:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:233:in 'block in ActiveRecord::Transactions::ClassMethods#transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/connection_pool.rb:416:in 'ActiveRecord::ConnectionAdapters::ConnectionPool#with_connection'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_handling.rb:312:in 'ActiveRecord::ConnectionHandling#with_connection'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:232:in 'ActiveRecord::Transactions::ClassMethods#transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:35:in 'SeedFu::Runner#run_file'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:26:in 'block in SeedFu::Runner#run'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:25:in 'Array#each'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:25:in 'SeedFu::Runner#run'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/discourse-seed-fu.rb:29:in 'SeedFu.seed'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/tasks/seed_fu.rake:36:in 'block (2 levels) in <main>'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:281:in 'block in Rake::Task#execute'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:281:in 'Array#each'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:281:in 'Rake::Task#execute'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:219:in 'block in Rake::Task#invoke_with_call_chain'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:199:in 'Monitor#synchronize'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:199:in 'Rake::Task#invoke_with_call_chain'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:188:in 'Rake::Task#invoke'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:183:in 'Rake::Application#invoke_task'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:139:in 'block (2 levels) in Rake::Application#top_level'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:139:in 'Array#each'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:139:in 'block in Rake::Application#top_level'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:148:in 'Rake::Application#run_with_threads'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:133:in 'Rake::Application#top_level'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:84:in 'block in Rake::Application#run'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:209:in 'Rake::Application#standard_exception_handling'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:81:in 'Rake::Application#run'
bin/rake:13:in '<top (required)>'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:59:in 'Kernel.load'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:59:in 'Bundler::CLI::Exec#kernel_load'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:23:in 'Bundler::CLI::Exec#run'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:452:in 'Bundler::CLI#exec'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/command.rb:28:in 'Bundler::Thor::Command#run'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in 'Bundler::Thor::Invocation#invoke_command'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor.rb:538:in 'Bundler::Thor.dispatch'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:35:in 'Bundler::CLI.dispatch'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/base.rb:584:in 'Bundler::Thor::Base::ClassMethods#start'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:29:in 'Bundler::CLI.start'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/exe/bundle:28:in 'block in <top (required)>'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/friendly_errors.rb:117:in 'Bundler.with_friendly_errors'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/exe/bundle:20:in '<top (required)>'
/usr/local/bin/bundle:25:in 'Kernel#load'
/usr/local/bin/bundle:25:in '<main>'
Tasks: TOP => db:seed_fu
2 « J'aime »

Où est passée le bouton « Moi aussi ! » ?

Cela se produit lorsque vous avez un groupe ou un utilisateur (!!!) existant nommé anonymous.

Nous avons de nombreux forums où anonymous a été utilisé comme nom d’utilisateur après une importation.
Le commit mentionne :

Cette PR introduit deux nouveaux groupes automatiques : anonymous_users et logged_in_users

mais apparemment, le groupe a fini par être nommé anonymous sans _users.

C’est regrettable car :

  • anonymous rend flou s’il s’agit d’un groupe d’utilisateurs ou d’un seul utilisateur
  • le risque de conflit avec un groupe ou un utilisateur existant est beaucoup plus élevé sans le _users

Solutions suggérées :
1 - nommer le groupe anonymous_users après tout, cela est plus cohérent avec logged_in_users et réduit énormément le risque de conflit
2 - détecter au moins le conflit et renommer l’utilisateur ou le groupe existant au lieu de générer une erreur

2 « J'aime »

Je regarde cela.

Il existe une migration pour gérer ce conflit de nom, donc je ne sais pas pourquoi cela ne fonctionne pas dans le cas de l’OP :

Lancez-vous cela seul avant d’exécuter les migrations ?

Votre migration examine les groupes existants. Cependant, elle devrait également examiner les utilisateurs existants, car les utilisateurs et les groupes partagent un même espace de noms et il est impossible d’avoir un groupe portant le même nom qu’un utilisateur.

Hmm d’accord, c’est assez agaçant… je crée une PR pour faire cela :

Ce qui, comme vous le dites, devrait réduire considérablement les chances de conflit. Je doute fort qu’il existe un utilisateur nommé anonymous_users, donc je ne pense pas qu’une vérification ou une migration automatisée soit nécessaire si je procède à ce renommage.

1 « J'aime »

Bon, cela devrait faire l’affaire. Cela résout également certains problèmes liés à la logique de modification à venir pour Granular group-based permissions for anonymous and logged in users :

https://github.com/discourse/discourse/pull/40435

2 « J'aime »

Non, pas au début. Cela échouait lors des migrations pendant le processus de reconstruction standard, ce qui a attiré mon attention initialement et m’a permis de remonter à la source. J’exécutais ensuite la tâche rake manuellement pour le dépannage.

2 « J'aime »

Bon, j’ai fusionné la PR maintenant, cela devrait régler les conflits de noms.

1 « J'aime »

C’est une excellente nouvelle !

Pourriez-vous également rétroporter la correction vers 2026.5 ?