Skip to main content

Overview

Throw UnrecoverableError from a job processor to immediately fail the job, bypassing any remaining retry attempts.

Class

class UnrecoverableError extends Error {
  constructor(message?: string);
}

Usage

Use UnrecoverableError when a job fails in a way that retrying won’t help:
import { Worker, UnrecoverableError } from 'bullmq';

const worker = new Worker('myQueue', async (job) => {
  // Validation errors shouldn't be retried
  if (!job.data.userId) {
    throw new UnrecoverableError('Missing required field: userId');
  }
  
  // Business logic errors
  const user = await getUser(job.data.userId);
  if (!user) {
    throw new UnrecoverableError(`User ${job.data.userId} not found`);
  }
  
  if (user.status === 'deleted') {
    throw new UnrecoverableError('Cannot process job for deleted user');
  }
  
  // Process the job
  return await processUser(user);
});

Examples

Validation Errors

import { UnrecoverableError } from 'bullmq';

const processor = async (job) => {
  // Validate required fields
  if (!job.data.email) {
    throw new UnrecoverableError('Email is required');
  }
  
  // Validate email format
  if (!isValidEmail(job.data.email)) {
    throw new UnrecoverableError('Invalid email format');
  }
  
  // Validate business rules
  if (job.data.amount < 0) {
    throw new UnrecoverableError('Amount cannot be negative');
  }
  
  return await sendEmail(job.data.email);
};

Resource Not Found

const processor = async (job) => {
  const resource = await db.findById(job.data.resourceId);
  
  if (!resource) {
    throw new UnrecoverableError(
      `Resource ${job.data.resourceId} not found`
    );
  }
  
  return await processResource(resource);
};

Permission Errors

const processor = async (job) => {
  const user = await getUser(job.data.userId);
  
  if (!user.hasPermission('admin')) {
    throw new UnrecoverableError(
      'User does not have required permissions'
    );
  }
  
  return await performAdminAction(job.data);
};

Configuration Errors

const processor = async (job) => {
  const config = await getConfig(job.data.configId);
  
  if (!config.apiKey) {
    throw new UnrecoverableError(
      'API key not configured for this service'
    );
  }
  
  return await callExternalAPI(config.apiKey, job.data);
};

Conditional Retry Logic

const processor = async (job) => {
  try {
    return await callAPI(job.data);
  } catch (error) {
    // 4xx errors shouldn't be retried
    if (error.statusCode >= 400 && error.statusCode < 500) {
      throw new UnrecoverableError(
        `API returned ${error.statusCode}: ${error.message}`
      );
    }
    
    // 5xx errors can be retried
    throw error;
  }
};

Comparison with Regular Errors

Regular Error (with retries)

const worker = new Worker('myQueue', async (job) => {
  // This will retry based on job.opts.attempts
  throw new Error('Temporary failure');
}, {
  connection: { host: 'localhost', port: 6379 },
});

await queue.add('task', { data }, {
  attempts: 3,  // Will retry 3 times
  backoff: {
    type: 'exponential',
    delay: 1000,
  },
});

UnrecoverableError (no retries)

const worker = new Worker('myQueue', async (job) => {
  // This will fail immediately, ignoring attempts setting
  throw new UnrecoverableError('Validation failed');
}, {
  connection: { host: 'localhost', port: 6379 },
});

await queue.add('task', { data }, {
  attempts: 3,  // Ignored when UnrecoverableError is thrown
});

Error Message

Provide clear error messages to help with debugging:
const processor = async (job) => {
  const user = await getUser(job.data.userId);
  
  if (!user) {
    throw new UnrecoverableError(
      `User not found: ${job.data.userId}. ` +
      `Job ID: ${job.id}, Created: ${new Date(job.timestamp).toISOString()}`
    );
  }
  
  return await processUser(user);
};

Handling Failed Jobs

Listen for failed events to handle unrecoverable errors:
import { Worker, UnrecoverableError } from 'bullmq';

const worker = new Worker('myQueue', processor);

worker.on('failed', (job, error) => {
  if (error.name === 'UnrecoverableError') {
    console.log(`Job ${job.id} failed with unrecoverable error:`, error.message);
    
    // Send alert, log to monitoring service, etc.
    sendAlert({
      jobId: job.id,
      error: error.message,
      data: job.data,
    });
  } else {
    console.log(`Job ${job.id} failed:`, error.message);
  }
});

When to Use UnrecoverableError

Use UnrecoverableError for:
  • Validation errors: Missing or invalid input data
  • Resource not found: Database records that don’t exist
  • Permission denied: Authorization failures
  • Configuration errors: Missing API keys or invalid settings
  • Business rule violations: Logical errors that won’t change with retries
  • 4xx HTTP errors: Client errors from external APIs
Don’t use UnrecoverableError for:
  • Network errors: Temporary connection issues
  • 5xx HTTP errors: Server errors that might be temporary
  • Rate limiting: Use RateLimitError instead
  • Timeouts: These might succeed on retry
  • Database connection errors: Temporary infrastructure issues

Build docs developers (and LLMs) love