n8n-io/n8n · error · InvalidScheduleError

one_off.fireAt must be a valid Date

Error message

one_off.fireAt must be a valid Date

What it means

Thrown by validateOneOff when schedule.fireAt is not a Date instance or is an Invalid Date (getTime() returns NaN). Like the cron validator, oneOff guards `unknown` because raw DB rows can arrive untyped - a string timestamp or a number would otherwise produce a confusing downstream error.

Source

Thrown at packages/@n8n/scheduler/src/core/recurrence/kinds/one-off.ts:13

import { InvalidScheduleError } from '../../errors';
import type { OneOffSchedule, ScheduledJob } from '../../types';
import { required } from '../field';

/**
 * Checks that a one-off schedule has a valid fire time.
 * @param schedule The one-off schedule to check.
 * @throws {InvalidScheduleError} When `fireAt` is not a valid Date.
 */
export function validateOneOff(schedule: OneOffSchedule): void {
	const fireAt: unknown = schedule.fireAt;
	if (!(fireAt instanceof Date) || Number.isNaN(fireAt.getTime())) {
		throw new InvalidScheduleError('one_off.fireAt must be a valid Date');
	}
}

/**
 * The one-off fire time, or `null` if it has already passed.
 * @param schedule The one-off schedule.
 * @param after The instant to fire after.
 * @returns `fireAt` when it is still ahead of `after`, otherwise `null`.
 */
export function oneOffNextRun(schedule: OneOffSchedule, after: Date): Date | null {
	return after.getTime() < schedule.fireAt.getTime() ? schedule.fireAt : null;
}

/**
 * The single fire of a one-off schedule: just `first`, then nothing.
 * @param first The one-off's fire time.
 * @returns A generator that yields `first` once.
 */

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Map the DB column to a Date in your ORM/driver config (TypeORM: { type: 'timestamp' } yields Date).
  2. When deserializing from JSON, wrap: fireAt: new Date(raw.fireAt).
  3. Validate with `value instanceof Date && !Number.isNaN(value.getTime())` before validateOneOff.
  4. Reject rows whose fire_at cannot be parsed rather than passing them through.

Example fix

// before
validateOneOff({ kind: 'one_off', fireAt: row.fire_at }); // row.fire_at is a string -> throws

// after - coerce at the boundary
const fireAt = new Date(row.fire_at as string);
if (Number.isNaN(fireAt.getTime())) {
  throw new Error(`row ${row.id} has unparseable fire_at`);
}
validateOneOff({ kind: 'one_off', fireAt });
Defensive patterns

Strategy: type-guard

Validate before calling

function coerceFireAt(raw: unknown): Date {
  const d = raw instanceof Date ? raw : new Date(raw as string);
  if (!(d instanceof Date) || Number.isNaN(d.getTime())) {
    throw new Error('fireAt must be a valid Date or ISO string');
  }
  return d;
}

validateOneOff({ kind: 'one_off', fireAt: coerceFireAt(row.fire_at) });

Type guard

function isValidFireAt(v: unknown): v is Date {
  return v instanceof Date && !Number.isNaN(v.getTime());
}

Prevention

When it happens

Trigger: A one_off schedule loaded from a DB row where fire_at came back as a string (ORM not configured for Date), a number (epoch ms), null, or an Invalid Date from new Date('invalid'). Also a literal constructed with fireAt: '2025-01-01'.

Common situations: An ORM/driver that returns TIMESTAMP as ISO string unless explicitly mapped; JSON-deserialized input (JSON.parse yields string, not Date); new Date(undefined) producing Invalid Date; a test fixture that set fireAt: Date.now() (number) instead of new Date().

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/e9dae409ed80720a. Report an issue: GitHub.