{"record":{"id":"91a06a5ab222aa13","repo":"heygen-com/hyperframes","slug":"engine-config-snapshot-must-be-a-plain-object","errorCode":null,"errorMessage":"Engine config snapshot must be a plain object","messagePattern":"Engine config snapshot must be a plain object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/engine/src/config.ts","lineNumber":516,"sourceCode":"    \"pageSideCompositingAutoDisabled\",\n    \"forceScreenshotExplicitlyOptedOut\",\n    \"streamingEncodeAutoDisabledOnWin32Compound\",\n  ] as const) {\n    if (config[field] !== undefined && typeof config[field] !== \"boolean\") {\n      throw new Error(`Engine config ${field} must be a boolean`);\n    }\n  }\n}\n\n/**\n * Validate a complete EngineConfig crossing a JSON wire boundary.\n *\n * `resolveConfig()` intentionally accepts partial programmatic overrides, but\n * a serialized render request stores a resolved snapshot. Accepting a partial\n * snapshot would skip the orchestrator's `resolveConfig()` fallback entirely.\n */\nexport function validateEngineConfigSnapshot(value: unknown): asserts value is EngineConfig {\n  if (!isPlainObject(value)) throw new Error(\"Engine config snapshot must be a plain object\");\n  validateRequiredEngineConfigFields(value);\n  validateEngineConfigScalars(value);\n  validateEngineConfigParallelism(value);\n  validateEngineConfigVp9(value);\n  validateEngineConfigRuntime(value);\n  validateEngineConfigHdr(value);\n  validateOptionalEngineConfigFields(value);\n}\n\n/**\n * Reference canvas area for the baseline `protocolTimeout`: 1080p. A single CDP\n * call (`Runtime.callFunctionOn` seek+paint, or `Page.captureScreenshot`)\n * scales with the *output pixel area* it has to render/serialize — NOT with the\n * frame count (that governs total wall-clock, capped separately by the ffmpeg\n * streaming inactivity timeout). A fixed 300s ceiling intermittently kills\n * legitimate slow-but-valid renders on large canvases with\n * `Runtime.callFunctionOn timed out`, so we scale the per-call ceiling with\n * area.","sourceCodeStart":498,"sourceCodeEnd":534,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/engine/src/config.ts#L498-L534","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","solutions":["Ensure the value passed is a single plain object literal `{...}` (or JSON.parse of an object document).","If the payload is wrapped (e.g. `{ config: {...} }`), unwrap to the inner object first.","JSON.parse the inbound string and confirm `typeof === \"object\" && !Array.isArray && value !== null` before calling."],"exampleFix":"// before\nconst payload = JSON.parse(body); // e.g. an array or envelope\nvalidateEngineConfigSnapshot(payload);\n\n// after\nconst payload = JSON.parse(body);\nconst cfg = Array.isArray(payload) ? payload[0] : payload.config ?? payload;\nvalidateEngineConfigSnapshot(cfg);","handlingStrategy":"type-guard","validationCode":"function isPlainObject(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value)\n    && [Object.prototype, null].includes(Object.getPrototypeOf(value));\n}\n\nif (!isPlainObject(snapshot)) throw new Error(\"engine config snapshot must be a plain object\");","typeGuard":"function isPlainObject(value: unknown): value is Record<string, unknown> {\n  if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n  const proto = Object.getPrototypeOf(value);\n  return proto === Object.prototype || proto === null;\n}","tryCatchPattern":"try {\n  validateEngineConfigSnapshot(payload);\n} catch (e) {\n  if (/snapshot must be a plain object/.test(String(e))) {\n    throw new TypeError(`Malformed render request: expected a config object, got ${Array.isArray(payload) ? \"array\" : typeof payload}`, { cause: e });\n  }\n  throw e;\n}","preventionTips":["JSON.parse the inbound body and confirm it is a single object before validating.","Unwrap envelopes ({config: {...}}) before calling validateEngineConfigSnapshot.","Do not pass class instances; pass plain object literals."],"tags":["engine-config","validation","snapshot","type-guard"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}