mastra-ai/mastra · error · RangeError

heartbeatMs must be a finite number no greater than ${MAX_TI

Error message

heartbeatMs must be a finite number no greater than ${MAX_TIMEOUT_MS}

What it means

assertValidHeartbeatMs validates the heartbeatMs option used to add periodic SSE ': heartbeat' comments to server-sent-event streams. The interval must be undefined, <= 0 (disabling), or a finite number not exceeding MAX_TIMEOUT_MS; anything else throws this RangeError.

Source

Thrown at client-sdks/ai-sdk/src/sse-heartbeat.ts:15

const SSE_HEARTBEAT_BYTES = new TextEncoder().encode(': heartbeat\n\n');
const LF_BYTE = 10;
const MAX_TIMEOUT_MS = 2_147_483_647;

type StreamReadResult<T> = { done: false; value: T } | { done: true; value?: undefined };
type WakeReason = 'read' | 'heartbeat';

/** Throws when an enabled heartbeat interval cannot be scheduled with a timer. */
export function assertValidHeartbeatMs(heartbeatMs?: number): void {
  if (
    heartbeatMs !== undefined &&
    !(heartbeatMs <= 0) &&
    (!Number.isFinite(heartbeatMs) || heartbeatMs > MAX_TIMEOUT_MS)
  ) {
    throw new RangeError(`heartbeatMs must be a finite number no greater than ${MAX_TIMEOUT_MS}`);
  }
}

/**
 * Wraps an SSE `Response` so it emits periodic `: heartbeat` comments while the source is idle,
 * keeping connections alive through proxies that close idle streams.
 *
 * Heartbeats are only inserted between complete SSE frames. AI SDK serialization uses LF-delimited
 * frames, which this wrapper preserves and relies on.
 *
 * Returns the input response unchanged when `heartbeatMs` is omitted, is `<= 0`, or the response has
 * no body. Throws a `RangeError` when `heartbeatMs` is enabled but cannot be scheduled with a timer.
 */
export function withSseHeartbeat(response: Response, heartbeatMs?: number): Response {
  assertValidHeartbeatMs(heartbeatMs);
  if (heartbeatMs === undefined || heartbeatMs <= 0 || !response.body) {
    return response;
  }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Pass a finite positive heartbeatMs within MAX_TIMEOUT_MS (e.g. 15000 for 15 seconds).
  2. Pass 0 or a negative number explicitly to disable heartbeats, or omit the option.
  3. Guard config parsing: Number.isFinite(Number(env.HEARTBEAT_MS)) before use.
  4. Log/computed the value at startup to catch NaN/Infinity from bad math.

Example fix

// before
const heartbeatMs = Number(process.env.SSE_HEARTBEAT_MS) || Infinity;
withSseHeartbeat(response, { heartbeatMs });
// after
const parsed = Number(process.env.SSE_HEARTBEAT_MS);
const heartbeatMs = Number.isFinite(parsed) && parsed > 0 ? parsed : 15000;
withSseHeartbeat(response, { heartbeatMs });
Defensive patterns

Strategy: validation

Validate before calling

function isUsableHeartbeatMs(v: unknown): v is number {
  return typeof v === 'number' && Number.isFinite(v) && (v <= 0 || v <= 2147483647);
}

Type guard

function isValidHeartbeatMs(v: number | undefined): boolean {
  return v === undefined || (Number.isFinite(v) && (v <= 0 || v <= MAX_TIMEOUT_MS));
}

Try / catch

try {
  withSseHeartbeat(res, { heartbeatMs });
} catch (e) {
  if (e instanceof RangeError && e.message.startsWith('heartbeatMs')) {
    withSseHeartbeat(res, { heartbeatMs: 15000 });
  } else throw e;
}

Prevention

When it happens

Trigger: Passing heartbeatMs: Infinity, NaN, or a value larger than MAX_TIMEOUT_MS to chatRoute or withSseHeartbeat; computing an interval from config math that yields NaN or Infinity (e.g. division by a zero/falsy variable).

Common situations: Env-var config parsed with Number() returning NaN; a misconfigured 'unlimited' sentinel like Number.MAX_SAFE_INTEGER or Infinity; unit mismatch (milliseconds vs seconds) producing an out-of-range value.

Understand the failure class

Background: Invalid option value errors: "must be one of", "is not a valid", and "only allows" failures explained — this error's family across 23 libraries.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/70c5b09d79a6ffdf. Report an issue: GitHub.