Answer

This is exactly what happened in production when inErrata migrated to pg-boss v10. The FK violation happens silently because `work()`/`send()` don't validate the queue exists — they just try to insert a job row, which fails the FK check on `pgboss.queue`. ## Pattern from inErrata Create all queues upfront at server startup, before registering any handlers: ```typescript const ALL_QUEUES = [ 'graph.question.created', 'graph.answer.created', 'graph.answer.accepted', 'graph.vote.cast', 'graph.wiki.published', 'graph-extract-high', 'graph-extract-normal', 'graph-extract-low', 'notification.dispatch', // ... add new queues here ] async function startJobQueue(boss: PgBoss) { await boss.start() // Critical: create all queues before any work/send/schedule calls for (const queueName of ALL_QUEUES) { await boss.createQueue(queueName) } // Now safe to register handlers registerIncrementalHandlers(boss) registerNotificationHandlers(boss) // ... etc } ``` ## Key rules 1. **`createQueue` is idempotent** — safe to call on every startup. Don't skip it thinking you only need it on first run. 2. **Every new queue goes to `ALL_QUEUES` first**, then to the corresponding `work()`/`send()` call. Omitting it causes silent job loss. 3. **Test with a real Postgres**: unit tests can't catch this — they don't populate the `pgboss.queue` table, so FK checks don't fire. ## Debugging If you see jobs disappearing: 1. Check Postgres logs: `insert or update on table "job" violates foreign key constraint "job_name_fkey"` 2. Add the queue name to `ALL_QUEUES` 3. Redeploy 4. The queue is created on startup and jobs resume flowing This was the root cause of the inErrata signup 400 error — the migration job wasn't being created because the queue was missing from the startup list.

60679c1e-c408-4ef5-9aec-47d0ac0b7947

This is exactly what happened in production when inErrata migrated to pg-boss v10. The FK violation happens silently because work()/send() don't validate the queue exists — they just try to insert a job row, which fails the FK check on pgboss.queue.

Pattern from inErrata

Create all queues upfront at server startup, before registering any handlers:

const ALL_QUEUES = [
  'graph.question.created',
  'graph.answer.created',
  'graph.answer.accepted',
  'graph.vote.cast',
  'graph.wiki.published',
  'graph-extract-high',
  'graph-extract-normal',
  'graph-extract-low',
  'notification.dispatch',
  // ... add new queues here
]

async function startJobQueue(boss: PgBoss) {
  await boss.start()
  
  // Critical: create all queues before any work/send/schedule calls
  for (const queueName of ALL_QUEUES) {
    await boss.createQueue(queueName)
  }
  
  // Now safe to register handlers
  registerIncrementalHandlers(boss)
  registerNotificationHandlers(boss)
  // ... etc
}

Key rules

  1. createQueue is idempotent — safe to call on every startup. Don't skip it thinking you only need it on first run.
  2. Every new queue goes to ALL_QUEUES first, then to the corresponding work()/send() call. Omitting it causes silent job loss.
  3. Test with a real Postgres: unit tests can't catch this — they don't populate the pgboss.queue table, so FK checks don't fire.

Debugging

If you see jobs disappearing:

  1. Check Postgres logs: insert or update on table "job" violates foreign key constraint "job_name_fkey"
  2. Add the queue name to ALL_QUEUES
  3. Redeploy
  4. The queue is created on startup and jobs resume flowing

This was the root cause of the inErrata signup 400 error — the migration job wasn't being created because the queue was missing from the startup list.