denoland/deno · error · TypeError
expireIn must be a non-negative integer: received ${expireIn
Error message
expireIn must be a non-negative integer: received ${expireIn} What it means
Deno.Kv expireIn (used by kv.set/get with { expireIn } and in atomic checks/mutations) must be a non-negative integer number of milliseconds. The guard rejects NaN, Infinity, fractional values, and negatives because a non-finite or fractional value would otherwise reach the native layer and overflow/panic while computing the absolute expiry timestamp.
Source
Thrown at ext/kv/01_db.ts:86
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}`,
);
}
}
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 ||View on GitHub (pinned to 89f33cbef2)
Solutions
- Always compute expireIn as an integer ms count: const ttlMs = Math.floor(ttlSeconds * 1000).
- Validate user/config input: Number.isInteger(n) && n >= 0 before calling set.
- Omit expireIn entirely (undefined is allowed) for no expiry.
- Convert string config values with Number() and validate finiteness.
Example fix
// before
await kv.set(["session", id], data, { expireIn: ttlSeconds }); // e.g. 3600.5 or seconds-as-ms
// after
const expireIn = Math.floor(ttlSeconds * 1000);
await kv.set(["session", id], data, { expireIn }); Defensive patterns
Strategy: validation
Validate before calling
function toExpireIn(ms: unknown): number | undefined {
if (ms === undefined || ms === null) return undefined;
const n = Math.floor(Number(ms));
if (!Number.isFinite(n) || n < 0) throw new Error(`invalid expireIn: ${ms}`);
return n;
}
await kv.set(key, value, { expireIn: toExpireIn(ttlMs) }); Type guard
function isValidExpireIn(v: unknown): v is number { return typeof v === "number" && Number.isInteger(v) && v >= 0; } Prevention
- Convert second-based TTLs to integer ms (Math.floor(sec * 1000)).
- Validate config-driven expirations with Number.isInteger at the config layer.
- Omit expireIn rather than passing 0/null/strings when no expiry is wanted.
When it happens
Trigger: kv.set(key, val, { expireIn: 1.5 }) fractional; { expireIn: -1000 }; { expireIn: NaN } from arithmetic; { expireIn: Infinity } or { expireIn: 24 * 60 * 60 * 1000 * 365 } overflowing past safe integers is fine numerically but Infinity from division by 0 is not; expireIn: '3600' as a string coerces to NaN under NumberIsInteger.
Common situations: TTL configs in seconds passed directly as ms (or vice versa producing fractions after division); expirations computed from user input; JSON strings not converted to numbers; clock arithmetic with undefined; porting Redis TTL semantics (seconds) to Deno KV (milliseconds).
Related errors
- Delay must be >= 0: received ${delay}
- Delay cannot be NaN
- Invalid backoffSchedule, interval at index ${i} is invalid
- Unexpected 'name' field in options, bench name is already pr
- Unexpected 'fn' field in options, bench function is already
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/e9123eb251c2b6d4.
Report an issue: GitHub.