denoland/deno · error · TypeError

Invalid backoffSchedule, max ${maxQueueBackoffIntervals} int

Error message

Invalid backoffSchedule, max ${maxQueueBackoffIntervals} intervals allowed

What it means

kv.enqueue accepts options.backoffSchedule, an array of retry intervals (ms) applied when a message handler fails. Deno limits it to at most 5 intervals (maxQueueBackoffIntervals); a longer array is rejected before the enqueue op runs.

Source

Thrown at ext/kv/01_db.ts:97

function validateExpireIn(expireIn: number | undefined) {
  if (expireIn === undefined) return;
  // Reject NaN, Infinity, fractional and negative values. A non-finite
  // expireIn otherwise reaches the native layer and overflows when computing
  // the absolute expiry, panicking the process.
  if (!NumberIsInteger(expireIn) || expireIn < 0) {
    throw new TypeError(
      `expireIn must be a non-negative integer: received ${expireIn}`,
    );
  }
}

const maxQueueBackoffIntervals = 5;
const maxQueueBackoffInterval = 60 * 60 * 1000;

function validateBackoffSchedule(backoffSchedule: number[]) {
  if (backoffSchedule.length > maxQueueBackoffIntervals) {
    throw new TypeError(
      `Invalid backoffSchedule, max ${maxQueueBackoffIntervals} intervals allowed`,
    );
  }
  for (let i = 0; i < backoffSchedule.length; ++i) {
    const interval = backoffSchedule[i];
    if (
      interval < 0 || interval > maxQueueBackoffInterval ||
      NumberIsNaN(interval)
    ) {
      throw new TypeError(
        `Invalid backoffSchedule, interval at index ${i} is invalid`,
      );
    }
  }
}

interface RawKvEntry {
  key: Deno.KvKey;

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Trim to at most 5 intervals: backoffSchedule.slice(0, 5).
  2. After the last interval, use the message's dead-letter behavior (a non-retryable failure path) instead of more stages.
  3. When generating, cap the loop at 5 iterations.

Example fix

// before
const backoffSchedule = Array.from({ length: 8 }, (_, i) => 2 ** i * 100);
await kv.enqueue(job, { backoffSchedule });

// after
const backoffSchedule = Array.from({ length: 5 }, (_, i) => 2 ** i * 100);
await kv.enqueue(job, { backoffSchedule });
Defensive patterns

Strategy: validation

Validate before calling

const MAX_INTERVALS = 5;
const backoffSchedule = rawSchedule
  .filter((i) => Number.isFinite(i) && i >= 0)
  .slice(0, MAX_INTERVALS);
await kv.enqueue(job, { backoffSchedule });

Type guard

function isValidBackoffSchedule(arr: unknown): arr is number[] { return Array.isArray(arr) && arr.length <= 5 && arr.every((i) => typeof i === "number" && Number.isFinite(i) && i >= 0 && i <= 60 * 60 * 1000); }

Prevention

When it happens

Trigger: kv.enqueue(msg, { backoffSchedule: [100, 1000, 10000, 60000, 300000, 1800000] }) with six entries; generating the schedule programmatically (e.g. exponential backoff loop of 7 steps); merging retry policies from another system with more stages.

Common situations: Porting RabbitMQ/SQS retry ladders with many stages; auto-generated exponential schedules (2^n for n up to 10); combining default plus custom schedules via array spread/concat.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/7a2accdcba9429ac. Report an issue: GitHub.