denoland/deno · error · TypeError

Delay cannot be greater than 30 days: received ${delay}

Error message

Delay cannot be greater than 30 days: received ${delay}

What it means

validateQueueDelay also enforces an upper bound: kv.enqueue's delay may not exceed 30 days (maxQueueDelay = 30*24*60*60*1000 ms). Longer deferrals are rejected because the queue's scheduler does not support month-scale delays.

Source

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

const encodeCursor: (
  selector: [Deno.KvKey | null, Deno.KvKey | null, Deno.KvKey | null],
  boundaryKey: Deno.KvKey,
) => string = (selector, boundaryKey) =>
  op_kv_encode_cursor(selector, boundaryKey);

async function openKv(path: string) {
  const rid = await op_kv_database_open(path);
  return new Kv(rid, kvSymbol);
}

const maxQueueDelay = 30 * 24 * 60 * 60 * 1000;

function validateQueueDelay(delay: number) {
  if (delay < 0) {
    throw new TypeError(`Delay must be >= 0: received ${delay}`);
  }
  if (delay > maxQueueDelay) {
    throw new TypeError(
      `Delay cannot be greater than 30 days: received ${delay}`,
    );
  }
  if (NumberIsNaN(delay)) {
    throw new TypeError("Delay cannot be NaN");
  }
}

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}`,
    );
  }

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Cap the delay at 30 days or schedule for <= 30 days out.
  2. For longer deferrals, enqueue a 'check' message that re-enqueues with another delay when the time hasn't come yet (chained enqueue).
  3. If you passed a timestamp by mistake, subtract now: delay = runAt - Date.now(), then clamp to [0, 30 days].

Example fix

// before
await kv.enqueue({ type: "renew" }, { delay: runAtMs }); // runAtMs ~ epoch -> huge

// after
const delay = Math.min(Math.max(0, runAtMs - Date.now()), 30 * 24 * 60 * 60 * 1000);
await kv.enqueue({ type: "renew" }, { delay });
Defensive patterns

Strategy: validation

Validate before calling

const MAX_DELAY = 30 * 24 * 60 * 60 * 1000;
const delay = Math.min(Math.max(0, runAt - Date.now()), MAX_DELAY);
await kv.enqueue(job, { delay });

Type guard

function isEnqueueDelay(d: unknown): d is number { return typeof d === "number" && Number.isFinite(d) && d >= 0 && d <= 30 * 24 * 60 * 60 * 1000; }

Prevention

When it happens

Trigger: kv.enqueue(msg, { delay: 45 * 24 * 60 * 60 * 1000 }); passing an absolute epoch timestamp as delay (seconds or ms); computing delay from a far-future calendar date; 'remind me in a year' features implemented directly on enqueue.

Common situations: Timestamp-vs-duration confusion (delay: Date.now() + week); long-term reminders or expirations; config-driven retry horizons larger than intended; copy from systems that allow arbitrary TTLs.

Related errors


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