# Migration issue with workflows

**URL:** https://meta.discourse.org/t/migration-issue-with-workflows/408090
**Category:** Bug
**Tags:** workflows
**Created:** [July 20, 2026, 10:10pm UTC](https://meta.discourse.org/t/migration-issue-with-workflows/408090 "2026-07-20T22:10:38Z")
**Posts on this page:** 4
**Page:** 1

<div class="post-metadata">

### Author: ![nicolsdennis](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/nicolsdennis/32/545883_2.png) [@nicolsdennis](https://meta.discourse.org/u/nicolsdennis)
#### Post date: [July 20, 2026, 10:10pm UTC](https://meta.discourse.org/t/migration-issue-with-workflows/408090/1 "2026-07-20T22:10:38Z")

</div>

Hi I don’t have Workflows enabled but it appears to be filling my log with entries.

Discourse | [v2026.7.0-latest +394](https://github.com/discourse/discourse/commit/c83bd321647f5e6e69e78089cd9b2aa605e903db "c83bd321647f5e6e69e78089cd9b2aa605e903db")

Job exception: PG::UndefinedTable: ERROR: relation “discourse\_workflows\_webhooks” does not exist LINE 10: WHERE a.attrelid = ‘“discourse\_workflows\_webhooks”’::regcla…

Message (252 copies reported)

Job exception: PG::UndefinedTable: ERROR: relation “discourse\_workflows\_webhooks” does not exist  
LINE 10: WHERE a.attrelid = ‘“discourse\_workflows\_webhooks”’::regcla…

```plaintext
^

Backtrace

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'

```

---

<div class="post-metadata">

### Author: ![supermathie](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/supermathie/32/507518_2.png) [@supermathie](https://meta.discourse.org/u/supermathie)
#### Post date: [July 20, 2026, 10:32pm UTC](https://meta.discourse.org/t/migration-issue-with-workflows/408090/2 "2026-07-20T22:32:40Z")

</div>

It looks like you have the plugin installed, but haven’t run migrations to create the necessary tables it uses.

Can you show us the part of your container definition where you install it?

---

<div class="post-metadata">

### Author: ![nicolsdennis](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/nicolsdennis/32/545883_2.png) [@nicolsdennis](https://meta.discourse.org/u/nicolsdennis)
#### Post date: [July 20, 2026, 10:46pm UTC](https://meta.discourse.org/t/migration-issue-with-workflows/408090/3 "2026-07-20T22:46:34Z")

</div>

Thanks, but discourse-workflows isn’t something I installed; it’s a bundled core plugin (ships in plugins/, authored by Discourse, behind enable\_discourse\_workflows), so there’s nothing in my app.yml for it.

The the table has a migration line 212 of `db/migrate/20260312000000_create_workflow_tables.rb` which creates `discourse_workflows_webhooks` but it never ran on my database.

Posting mainly in case it’s worth a guard on that scheduled job (PurgeExpiredWebhookTestListeners) so a missing table degrades quietly instead of erroring on every run.

---

<div class="post-metadata">

### Author: ![nicolsdennis](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/nicolsdennis/32/545883_2.png) [@nicolsdennis](https://meta.discourse.org/u/nicolsdennis)
#### Post date: [July 20, 2026, 11:00pm UTC](https://meta.discourse.org/t/migration-issue-with-workflows/408090/4 "2026-07-20T23:00:55Z")

</div>

The table `discourse_workflows_webhooks` does have a migration that creates it (`create_workflow_tables`, version `20260312000000`, the `create_table` sits near the bottom of that file).

On my database that migration is already recorded as run in `schema_migrations`, but the webhooks table itself was never created.

A clean `db:migrate` confirmed it: it ran with no output, so nothing was pending, yet the table still did not exist. Rails keys off the version number, sees the migration as done, and never runs it again, so the table stays missing while the model and the scheduled purge job keep loading and firing. That is why the same job exception kept piling up.

**The fix I applied:** Since the migration is recorded, running it again was not an option, because it would try to recreate the other workflow tables that already exist and fail.

So I created the missing table directly, matching the migration exactly (same columns, same five indexes including the unique one):

`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`

After that, `table_exists?` returns true and the `PurgeExpiredWebhookTestListeners` job stops erroring on its next run.

Because this came from a shipped migration being edited after it had already run, a later core update that adds a proper migration for this table could try to create it again and fail on “already exists”.

If that shows up after an update, the answer is to let the new migration win: drop the table I created by hand first, or insert its version into `schema_migrations` so it gets skipped.

Flagging it in case anyone else on the latest branch hits the same job exception.
