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
- Pass a finite positive heartbeatMs within MAX_TIMEOUT_MS (e.g. 15000 for 15 seconds).
- Pass 0 or a negative number explicitly to disable heartbeats, or omit the option.
- Guard config parsing: Number.isFinite(Number(env.HEARTBEAT_MS)) before use.
- 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
- Validate env vars with Number.isFinite before assigning numeric config.
- Never use Infinity/NaN as a 'default' sentinel for timeouts.
- Clamp computed intervals to MAX_TIMEOUT_MS.
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.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Google RBAC roleMapping is required.
- Cookie password must be at least 32 characters. Set OKTA_COO
- Expected a PEM-encoded public key or certificate string for
- terminationGraceMs must be greater than zero.
- Worker command must be a non-empty executable path.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/70c5b09d79a6ffdf.
Report an issue: GitHub.