heygen-com/hyperframes · error · Error

Engine config snapshot must be a plain object

Error message

Engine config snapshot must be a plain object

What it means

validateEngineConfigSnapshot() is the entry guard and first asserts the top-level value is a plain object (prototype is Object.prototype or null). Arrays, class instances, null, primitives, or objects with a non-trivial prototype are rejected before any field validation runs. This protects the deep field-walking validators from receiving malformed input.

Source

Thrown at packages/engine/src/config.ts:516

    "pageSideCompositingAutoDisabled",
    "forceScreenshotExplicitlyOptedOut",
    "streamingEncodeAutoDisabledOnWin32Compound",
  ] as const) {
    if (config[field] !== undefined && typeof config[field] !== "boolean") {
      throw new Error(`Engine config ${field} must be a boolean`);
    }
  }
}

/**
 * Validate a complete EngineConfig crossing a JSON wire boundary.
 *
 * `resolveConfig()` intentionally accepts partial programmatic overrides, but
 * a serialized render request stores a resolved snapshot. Accepting a partial
 * snapshot would skip the orchestrator's `resolveConfig()` fallback entirely.
 */
export function validateEngineConfigSnapshot(value: unknown): asserts value is EngineConfig {
  if (!isPlainObject(value)) throw new Error("Engine config snapshot must be a plain object");
  validateRequiredEngineConfigFields(value);
  validateEngineConfigScalars(value);
  validateEngineConfigParallelism(value);
  validateEngineConfigVp9(value);
  validateEngineConfigRuntime(value);
  validateEngineConfigHdr(value);
  validateOptionalEngineConfigFields(value);
}

/**
 * Reference canvas area for the baseline `protocolTimeout`: 1080p. A single CDP
 * call (`Runtime.callFunctionOn` seek+paint, or `Page.captureScreenshot`)
 * scales with the *output pixel area* it has to render/serialize — NOT with the
 * frame count (that governs total wall-clock, capped separately by the ffmpeg
 * streaming inactivity timeout). A fixed 300s ceiling intermittently kills
 * legitimate slow-but-valid renders on large canvases with
 * `Runtime.callFunctionOn timed out`, so we scale the per-call ceiling with
 * area.

View on GitHub (pinned to c2996c8626)

Solutions

  1. Ensure the value passed is a single plain object literal `{...}` (or JSON.parse of an object document).
  2. If the payload is wrapped (e.g. `{ config: {...} }`), unwrap to the inner object first.
  3. JSON.parse the inbound string and confirm `typeof === "object" && !Array.isArray && value !== null` before calling.

Example fix

// before
const payload = JSON.parse(body); // e.g. an array or envelope
validateEngineConfigSnapshot(payload);

// after
const payload = JSON.parse(body);
const cfg = Array.isArray(payload) ? payload[0] : payload.config ?? payload;
validateEngineConfigSnapshot(cfg);
Defensive patterns

Strategy: type-guard

Validate before calling

function isPlainObject(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value)
    && [Object.prototype, null].includes(Object.getPrototypeOf(value));
}

if (!isPlainObject(snapshot)) throw new Error("engine config snapshot must be a plain object");

Type guard

function isPlainObject(value: unknown): value is Record<string, unknown> {
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
  const proto = Object.getPrototypeOf(value);
  return proto === Object.prototype || proto === null;
}

Try / catch

try {
  validateEngineConfigSnapshot(payload);
} catch (e) {
  if (/snapshot must be a plain object/.test(String(e))) {
    throw new TypeError(`Malformed render request: expected a config object, got ${Array.isArray(payload) ? "array" : typeof payload}`, { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing a JSON-deserialized array, a class instance (e.g. a Map or a custom Config class with extra prototype), null, undefined, a string, or a plain JSON value that is not an object to validateEngineConfigSnapshot().

Common situations: Deserializing a render request whose body root is an array or a wrapped envelope; double-wrapping the config in another object; passing a Promise/thenable that resolved to a non-object; a malformed IPC payload.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/91a06a5ab222aa13. Report an issue: GitHub.