# Background queues have a dead-letter or error-handling path

- **Pattern:** `ab-000250` (`ai-slop-cost-bombs.job-hygiene.queue-deadletter-configured`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000250
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000250)

## Why it matters

Background job queues without a dead-letter path are silent failure sinks: a job that consistently fails disappears from the active queue after its retry cap is exhausted, leaving no record of what went wrong unless you explicitly configure failure handling. For payment-adjacent jobs — subscription renewals, fulfillment triggers, invoice generation — silent failure means lost revenue and customer confusion. Even for non-financial jobs, the absence of a failure path makes debugging a production incident nearly impossible: you know something is wrong because users report it, but the queue gives you no evidence. The observability gap is the core risk, not the job failure itself.

## Severity rationale

Low because a missing dead-letter path does not cause immediate harm but compounds debugging difficulty and hides systematic failures until user-reported impact escalates.

## Remediation

Add a `failed` event handler to every Worker and route failures to your alerting stack. In `src/lib/worker.ts` or wherever workers are initialized:

```ts
import * as Sentry from '@sentry/node'

const worker = new Worker('payments', async (job) => {
  // ... job processor
})

worker.on('failed', (job, err) => {
  console.error(`[queue] Job ${job?.id} (${job?.name}) failed after ${job?.attemptsMade} attempts:`, err)
  Sentry.captureException(err, { extra: { jobId: job?.id, jobName: job?.name } })
})

worker.on('error', (err) => {
  Sentry.captureException(err)
})
```

Set `removeOnFail: { count: 500 }` to keep the last 500 failed jobs in Redis for post-mortem inspection without unbounded storage growth.

## Detection

- **ID:** `queue-deadletter-configured`
- **Severity:** `low`
- **What to look for:** When a background job library is in dependencies, walk source files for queue/worker setup calls: `new Queue(`, `new Worker(`, `inngest.createFunction(`, `client.publish(`. Count all queue setups and verify each has an error-handling configuration: a `failed` event handler (`worker.on('failed', ...)`), a `removeOnFail` option set, a `deadLetterQueue` config, an `inngest` `onFailure` handler, OR a try/catch around the job processor that logs/alerts.
- **Pass criteria:** 100% of queue setups have error-handling configured. Report: "X queue setups inspected, Y with error handling, 0 silent-failure."
- **Fail criteria:** At least 1 queue setup has no error-handling path.
- **Skip (N/A) when:** No background job library in dependencies.
- **Detail on fail:** `"1 silent-failure queue: src/lib/worker.ts new Worker('payments', processor) has no .on('failed', ...) listener — failed jobs disappear with no log"`
- **Remediation:** Without error handling, failed jobs vanish silently — you only find out when customers complain. Add a failure listener:

  ```ts
  // Bad: failures are silent
  const worker = new Worker('payments', async (job) => { ... })

  // Good: log and alert on failure
  worker.on('failed', (job, err) => {
    console.error(`Job ${job.id} failed:`, err)
    // ... send to Sentry, etc.
  })
  ```

---

## External references

- iso-25010 reliability

Taxons: observability, error-resilience

HTML version: https://auditbuffet.com/patterns/ab-000250
