anomalyco/sst · error · TypeError

waitUntil requires a valid Date

Error message

waitUntil requires a valid Date

What it means

resolveWaitUntilDuration converts a Date passed to durable workflow waitUntil() into a Duration. It throws a TypeError when Date.getTime() returns NaN/Infinity, i.e. the argument is not a valid Date. This guards against scheduling a wait on an unusable timestamp.

Source

Thrown at sdk/js/src/aws/workflow.ts:797

        : parseTimestamp(execution.EndTimestamp),
  };
}

function parseTimestamp(timestamp: string | number): Date {
  const value =
    typeof timestamp === "number" ? timestamp : Number(timestamp);

  if (Number.isFinite(value)) {
    return new Date(value < 1_000_000_000_000 ? value * 1000 : value);
  }

  return new Date(timestamp);
}

function resolveWaitUntilDuration(until: Date): durable.Duration {
  const timestamp = until.getTime();
  if (!Number.isFinite(timestamp)) {
    throw new TypeError("waitUntil requires a valid Date");
  }

  return {
    seconds: Math.max(0, Math.ceil((timestamp - Date.now()) / 1000)),
  };
}

function withRollback<
  TLogger extends durable.DurableLogger = durable.DurableLogger,
>(context: durable.DurableContext<TLogger>): workflow.Context<TLogger> {
  const wrapped = context as WrappedDurableContext<TLogger>;
  if (wrapped[rollbackStateSymbol]) return wrapped as workflow.Context<TLogger>;

  const rollbackState: RollbackState<TLogger> = { undoStack: [] };

  wrapped[rollbackStateSymbol] = rollbackState;

  Object.defineProperty(wrapped, "stepWithRollback", {

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Check the Date before passing it: Number.isFinite(date.getTime()) must be true.
  2. Trace where the Date is constructed — usually an upstream parse of missing or malformed data.
  3. Provide a fallback deadline (e.g. now + default timeout) when the source value is absent.
  4. Log the offending value to confirm it is Invalid Date and fix the parsing/format at the source.

Example fix

// before
const deadline = new Date(config.deadline);
await workflow.waitUntil(deadline);

// after
const deadline = new Date(config.deadline);
if (Number.isNaN(deadline.getTime())) {
  deadline.setTime(Date.now() + 60_000); // fallback: 1 minute from now
}
await workflow.waitUntil(deadline);
Defensive patterns

Strategy: validation

Validate before calling

export function assertValidDate(d: Date): Date {
  if (!(d instanceof Date) || !Number.isFinite(d.getTime())) {
    throw new TypeError(`waitUntil requires a valid Date, got ${d}`);
  }
  return d;
}
// usage: await workflow.waitUntil(assertValidDate(deadline));

Type guard

function isValidDate(value: unknown): value is Date {
  return value instanceof Date && Number.isFinite(value.getTime());
}

Try / catch

try {
  await workflow.waitUntil(deadline);
} catch (e) {
  if (e instanceof TypeError && /valid Date/.test(e.message)) {
    await workflow.waitUntil(new Date(Date.now() + 60_000)); // fallback
  } else throw e;
}

Prevention

When it happens

Trigger: Calling waitUntil() with an invalid Date — e.g. new Date(undefined), new Date("not-a-date"), a Date parsed from missing/malformed input — so until.getTime() is not finite.

Common situations: Parsing a deadline from an optional API field or environment variable that came back undefined or in an unexpected format; arithmetic like new Date(Date.parse(badString)); passing a non-Date value that was cast to Date.

Related errors


AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30). Data as JSON: /api/errors/64dd944fe08d119b. Report an issue: GitHub.