Bug de la nouvelle fonctionnalité Workflows

Bonjour, je n’ai pas activé les Workflows, mais il semble qu’il remplisse mon journal avec des entrées.

Discourse | v2026.7.0-latest +394

Exception de tâche : PG::UndefinedTable : ERREUR : la relation « discourse_workflows_webhooks » n’existe pas LIGNE 10 : WHERE a.attrelid = ‘“discourse_workflows_webhooks”’::regcla…

Message (252 copies signalées)

Exception de tâche : PG::UndefinedTable : ERREUR : la relation « discourse_workflows_webhooks » n’existe pas
LIGNE 10 : WHERE a.attrelid = ‘“discourse_workflows_webhooks”’::regcla…

^

Traceback

rack-mini-profiler-4.0.1/lib/patches/db/pg/alias_method.rb:109:in 'PG::Connection#exec'
rack-mini-profiler-4.0.1/lib/patches/db/pg/alias_method.rb:109:in 'PG::Connection#async_exec'
activerecord-8.0.5/lib/active_record/connection_adapters/postgresql/database_statements.rb:167:in 'ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#perform_query'
activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:556:in 'block (2 levels) in ActiveRecord::ConnectionAdapters::DatabaseStatements#raw_execute'
activerecord-8.0.5/lib/active_record/connection_adapters/abstract_adapter.rb:1022:in 'block in ActiveRecord::ConnectionAdapters::AbstractAdapter#with_raw_connection'
activesupport-8.0.5/lib/active_support/concurrency/null_lock.rb:9:in 'ActiveSupport::Concurrency::NullLock#synchronize'
activerecord-8.0.5/lib/active_record/connection_adapters/abstract_adapter.rb:991:in 'ActiveRecord::ConnectionAdapters::AbstractAdapter#with_raw_connection'
activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:555:in 'block in ActiveRecord::ConnectionAdapters::DatabaseStatements#raw_execute'
activesupport-8.0.5/lib/active_support/notifications/instrumenter.rb:58:in 'ActiveSupport::Notifications::Instrumenter#instrument'
activerecord-8.0.5/lib/active_record/connection_adapters/abstract_adapter.rb:1142:in 'ActiveRecord::ConnectionAdapters::AbstractAdapter#log'
activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:554:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#raw_execute'
activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:591:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#internal_execute'
activerecord-8.0.5/lib/active_record/connection_adapters/postgresql/database_statements.rb:15:in 'ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#query'
activerecord-8.0.5/lib/active_record/connection_adapters/postgresql_adapter.rb:1042:in 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#column_definitions'
activerecord-8.0.5/lib/active_record/connection_adapters/abstract/schema_statements.rb:109:in 'ActiveRecord::ConnectionAdapters::SchemaStatements#columns'
activerecord-8.0.5/lib/active_record/connection_adapters/schema_cache.rb:345:in 'block (2 levels) in ActiveRecord::ConnectionAdapters::SchemaCache#columns'
activerecord-8.0.5/lib/active_record/connection_adapters/abstract/connection_pool.rb:416:in 'ActiveRecord::ConnectionAdapters::ConnectionPool#with_connection'
activerecord-8.0.5/lib/active_record/connection_adapters/schema_cache.rb:344:in 'block in ActiveRecord::ConnectionAdapters::SchemaCache#columns'
activerecord-8.0.5/lib/active_record/connection_adapters/schema_cache.rb:343:in 'Hash#fetch'
activerecord-8.0.5/lib/active_record/connection_adapters/schema_cache.rb:343:in 'ActiveRecord::ConnectionAdapters::SchemaCache#columns'
activerecord-8.0.5/lib/active_record/connection_adapters/schema_cache.rb:354:in 'block in ActiveRecord::ConnectionAdapters::SchemaCache#columns_hash'
activerecord-8.0.5/lib/active_record/connection_adapters/schema_cache.rb:353:in 'Hash#fetch'
activerecord-8.0.5/lib/active_record/connection_adapters/schema_cache.rb:353:in 'ActiveRecord::ConnectionAdapters::SchemaCache#columns_hash'
activerecord-8.0.5/lib/active_record/connection_adapters/schema_cache.rb:54:in 'ActiveRecord::ConnectionAdapters::SchemaReflection#columns_hash'
activerecord-8.0.5/lib/active_record/connection_adapters/schema_cache.rb:198:in 'ActiveRecord::ConnectionAdapters::BoundSchemaReflection#columns_hash'
activerecord-8.0.5/lib/active_record/model_schema.rb:592:in 'ActiveRecord::ModelSchema::ClassMethods#load_schema!'
activerecord-8.0.5/lib/active_record/counter_cache.rb:187:in 'ActiveRecord::CounterCache::ClassMethods#load_schema!'
activerecord-8.0.5/lib/active_record/encryption/encryptable_record.rb:127:in 'ActiveRecord::Encryption::EncryptableRecord::ClassMethods#load_schema!'
activerecord-8.0.5/lib/active_record/model_schema.rb:539:in 'block in ActiveRecord::ModelSchema::ClassMethods#load_schema'
activerecord-8.0.5/lib/active_record/model_schema.rb:536:in 'Monitor#synchronize'
activerecord-8.0.5/lib/active_record/model_schema.rb:536:in 'ActiveRecord::ModelSchema::ClassMethods#load_schema'
activerecord-8.0.5/lib/active_record/model_schema.rb:428:in 'ActiveRecord::ModelSchema::ClassMethods#columns_hash'
activerecord-8.0.5/lib/active_record/attributes.rb:255:in 'block in ActiveRecord::Attributes::ClassMethods#_default_attributes'
activerecord-8.0.5/lib/active_record/connection_adapters/abstract/connection_pool.rb:422:in 'ActiveRecord::ConnectionAdapters::ConnectionPool#with_connection'
activerecord-8.0.5/lib/active_record/connection_handling.rb:312:in 'ActiveRecord::ConnectionHandling#with_connection'
activerecord-8.0.5/lib/active_record/attributes.rb:254:in 'ActiveRecord::Attributes::ClassMethods#_default_attributes'
activemodel-8.0.5/lib/active_model/attribute_registration.rb:38:in 'ActiveModel::AttributeRegistration::ClassMethods#attribute_types'
activemodel-8.0.5/lib/active_model/attribute_registration.rb:49:in 'ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute'
activerecord-8.0.5/lib/active_record/type_caster/map.rb:16:in 'ActiveRecord::TypeCaster::Map#type_for_attribute'
activerecord-8.0.5/lib/arel/table.rb:107:in 'Arel::Table#type_for_attribute'
activerecord-8.0.5/lib/active_record/table_metadata.rb:18:in 'ActiveRecord::TableMetadata#type'
activerecord-8.0.5/lib/active_record/relation/predicate_builder.rb:59:in 'ActiveRecord::PredicateBuilder#build'
activerecord-8.0.5/lib/active_record/relation/predicate_builder.rb:54:in 'ActiveRecord::PredicateBuilder#
'
activerecord-8.0.5/lib/active_record/relation/predicate_builder.rb:145:in 'block in ActiveRecord::PredicateBuilder#expand_from_hash'
activerecord-8.0.5/lib/active_record/relation/predicate_builder.rb:87:in 'Hash#each'
activerecord-8.0.5/lib/active_record/relation/predicate_builder.rb:87:in 'Enumerable#flat_map'
activerecord-8.0.5/lib/active_record/relation/predicate_builder.rb:87:in 'ActiveRecord::PredicateBuilder#expand_from_hash'
activerecord-8.0.5/lib/active_record/relation/predicate_builder.rb:25:in 'ActiveRecord::PredicateBuilder#build_from_hash'
activerecord-8.0.5/lib/active_record/relation/query_methods.rb:1643:in 'ActiveRecord::QueryMethods#build_where_clause'
activerecord-8.0.5/lib/active_record/relation/query_methods.rb:1044:in 'ActiveRecord::QueryMethods#where!'
activerecord-8.0.5/lib/active_record/relation/query_methods.rb:1039:in 'ActiveRecord::QueryMethods#where'
/var/www/discourse/plugins/discourse-workflows/app/models/discourse_workflows/webhook.rb:12:in 'block in class:Webhook'
activerecord-8.0.5/lib/active_record/relation.rb:565:in 'BasicObject#instance_exec'
activerecord-8.0.5/lib/active_record/relation.rb:565:in 'block in ActiveRecord::Relation#_exec_scope'
activerecord-8.0.5/lib/active_record/relation.rb:1383:in 'ActiveRecord::Relation#_scoping'
activerecord-8.0.5/lib/active_record/relation.rb:565:in 'ActiveRecord::Relation#_exec_scope'
activerecord-8.0.5/lib/active_record/scoping/named.rb:175:in 'block in DiscourseWorkflows::Webhook.scope'
/var/www/discourse/plugins/discourse-workflows/lib/discourse_workflows/webhook_test_listener.rb:88:in 'DiscourseWorkflows::WebhookTestListener.purge_expired!'
/var/www/discourse/plugins/discourse-workflows/app/jobs/scheduled/discourse_workflows/purge_expired_webhook_test_listeners.rb:9:in 'Jobs::DiscourseWorkflows::PurgeExpiredWebhookTestListeners#execute'
/var/www/discourse/app/jobs/base.rb:313:in 'block (2 levels) in Jobs::Base#perform'
rails_multisite-7.0.0/lib/rails_multisite/connection_management/null_instance.rb:49:in 'RailsMultisite::ConnectionManagement::NullInstance#with_connection'
rails_multisite-7.0.0/lib/rails_multisite/connection_management.rb:17:in 'RailsMultisite::ConnectionManagement.with_connection'
/var/www/discourse/app/jobs/base.rb:300:in 'block in Jobs::Base#perform'
/var/www/discourse/app/jobs/base.rb:297:in 'Array#each'
/var/www/discourse/app/jobs/base.rb:297:in 'Jobs::Base#perform'
/var/www/discourse/app/jobs/base.rb:375:in 'Jobs::Scheduled#perform'
mini_scheduler-0.20.0/lib/mini_scheduler/manager.rb:137:in 'MiniScheduler::Manager::Runner#process_queue'
mini_scheduler-0.20.0/lib/mini_scheduler/manager.rb:77:in 'MiniScheduler::Manager::Runner#worker_loop'
mini_scheduler-0.20.0/lib/mini_scheduler/manager.rb:63:in 'block (2 levels) in MiniScheduler::Manager::Runner#ensure_worker_threads'




Il semble que vous ayez installé le plugin, mais que vous n’ayez pas exécuté les migrations pour créer les tables nécessaires qu’il utilise.

Pouvez-vous nous montrer la partie de votre définition de conteneur où vous l’installez ?

Merci, mais discourse-workflows n’est pas quelque chose que j’ai installé ; c’est un plugin principal intégré (fourni dans le dossier plugins/, développé par Discourse, activé via enable_discourse_workflows), donc il n’y a rien à son sujet dans mon fichier app.yml.

La table possède une ligne de migration à la ligne 212 de db/migrate/20260312000000_create_workflow_tables.rb qui crée discourse_workflows_webhooks, mais celle-ci n’a jamais été exécutée sur ma base de données.

Je poste cela principalement au cas où il vaudrait la peine d’ajouter une vérification de sécurité pour ce travail planifié (PurgeExpiredWebhookTestListeners), afin qu’une table manquante entraîne une dégradation silencieuse plutôt qu’une erreur à chaque exécution.

1 « J'aime »

La table discourse_workflows_webhooks possède bien une migration qui la crée (create_workflow_tables, version 20260312000000, l’instruction create_table se trouve vers le bas de ce fichier).

Dans ma base de données, cette migration est déjà enregistrée comme exécutée dans schema_migrations, mais la table des webhooks n’a jamais été créée.

Un db:migrate propre l’a confirmé : il s’est exécuté sans afficher de sortie, donc rien n’était en attente, et pourtant la table n’existait toujours pas. Rails utilise le numéro de version comme clé, considère la migration comme terminée et ne l’exécute jamais à nouveau, ce qui laisse la table manquante tandis que le modèle et le job de purge planifié continuent de se charger et de s’exécuter. C’est pourquoi la même exception de job s’accumulait.

La correction que j’ai appliquée : Comme la migration est enregistrée, il n’était pas possible de l’exécuter à nouveau, car elle aurait tenté de recréer les autres tables de workflow qui existent déjà et aurait échoué.

J’ai donc créé la table manquante directement, en respectant exactement la migration (mêmes colonnes, mêmes cinq index, y compris l’unique) :

conn = ActiveRecord::Base.connection
unless conn.table_exists?(:discourse_workflows_webhooks)
conn.create_table :discourse_workflows_webhooks do |t|
t.bigint :workflow_id, null: false
t.string :workflow_version_id, limit: 36
t.string :node_name, null: false, limit: 100
t.string :webhook_path, null: false, limit: 500
t.string :http_method, null: false, limit: 10
t.string :webhook_id, limit: 36
t.integer :path_length
t.boolean :test_webhook, null: false, default: false
t.integer :user_id
t.jsonb :workflow_snapshot
t.datetime :expires_at
t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
end
conn.add_index :discourse_workflows_webhooks, %i[http_method webhook_path test_webhook], unique: true, name: "idx_dwf_webhooks_on_method_path_test"
conn.add_index :discourse_workflows_webhooks, %i[webhook_id http_method test_webhook], name: "idx_dwf_webhooks_on_webhook_id_method_test", where: "webhook_id IS NOT NULL"
conn.add_index :discourse_workflows_webhooks, :workflow_id, name: "idx_dwf_webhooks_on_workflow_id"
conn.add_index :discourse_workflows_webhooks, :workflow_version_id, name: "idx_dwf_webhooks_on_workflow_version_id"
conn.add_index :discourse_workflows_webhooks, :expires_at, name: "idx_dwf_webhooks_on_expires_at", where: "expires_at IS NOT NULL"
end

Après cela, table_exists? renvoie true et le job PurgeExpiredWebhookTestListeners cesse de générer des erreurs lors de son prochain lancement.

Comme cela provient d’une migration livrée qui a été modifiée après avoir déjà été exécutée, une future mise à jour du noyau ajoutant une migration correcte pour cette table pourrait tenter de la recréer et échouer avec une erreur “déjà existante”.

Si cela apparaît après une mise à jour, la solution est de laisser la nouvelle migration prendre le pas : supprimez d’abord la table que j’ai créée manuellement, ou insérez sa version dans schema_migrations pour qu’elle soit ignorée.

Je signale ce problème au cas où d’autres personnes sur la dernière branche rencontreraient la même exception de job.

1 « J'aime »