{"record":{"id":"8f1c3e8be3ccfcb1","repo":"pydantic/monty","slug":"cannot-convert-constructorname-value-instance-to-a-monty","errorCode":null,"errorMessage":"Cannot convert ${constructorName(value)} instance to a Monty value — wrap it in ClassInstance(...)","messagePattern":"Cannot convert (.+?) instance to a Monty value — wrap it in ClassInstance\\(\\.\\.\\.\\)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/monty-js/ts/classInstance.ts","lineNumber":553,"sourceCode":"  if (marker === 'ClassInstance') {\n    // Identity-bearing markers are produced internally by this walk, never\n    // held by host code (`restore` maps them to the original object or a\n    // MontyClassProxy). One arriving here is forged — e.g. embedded\n    // in attacker-controlled JSON to impersonate a registered instance.\n    throw new TypeError('raw ClassInstance markers are not accepted — wrap the object in ClassInstance(...)')\n  }\n  if (marker === 'Type' && (value as { classType?: unknown }).classType !== undefined) {\n    // Same reasoning for a host-class marker; builtin `Type` markers\n    // (`{ value: 'int' }`) carry no identity and pass through.\n    throw new TypeError('raw Type markers are not accepted — pass the class through ClassType(...)')\n  }\n  if (marker !== undefined) {\n    return value\n  }\n  if (isPlainObject(value)) {\n    return walkPlainObject(value as Record<string, unknown>, walk)\n  }\n  throw new TypeError(\n    `Cannot convert ${constructorName(value)} instance to a Monty value — wrap it in ClassInstance(...)`,\n  )\n}\n\n/**\n * Inbound walk over a sandbox value reaching the host: maps `ClassInstance`\n * markers to the original wrapped object when the id is in `store` (identity\n * preserved), else to a [`MontyClassProxy`] proxy with recursively\n * restored attrs; maps a host-class `Type` marker to the registered class\n * object the same way (an unregistered class stays a marker); recurses into\n * containers. Wire values are already depth-bounded by the native layer, so\n * no guard is needed here.\n */\nexport function restore(value: unknown, store: InstanceStore): unknown {\n  if (typeof value !== 'object' || value === null) {\n    return value\n  }\n  const walk = (item: unknown) => restore(item, store)","sourceCodeStart":535,"sourceCodeEnd":571,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-js/ts/classInstance.ts#L535-L571","documentation":"Class instances of arbitrary host constructors cannot be serialized across the sandbox boundary; Monty only carries values it understands or explicitly registered class instances. The prepare walk throws this TypeError naming the constructor when it meets a non-plain object it cannot convert. Wrap the instance with ClassInstance(...) (with a ClassType) to expose it to the sandbox.","triggerScenarios":"Passing a Map, Set, Date, RegExp, class instance, or other exotic object (constructorName(value) reveals which) as a feedRun input, external-function return, or nested attribute without wrapping it.","commonSituations":"Returning ORM model instances or framework objects from callbacks; passing Date/Map values that were assumed JSON-convertible; forgetting to wrap an object registered elsewhere in the same call.","solutions":["Wrap the instance: ClassInstance(instance, ClassType(SomeClass))","Convert the value to a plain object/array/primitive before passing it","Register the class once via the class-registration API and consistently wrap every instance you send"],"exampleFix":"// before\nsession.feedRun('describe(user)', { inputs: { user: new User('ada') } });\n// after\nsession.feedRun('describe(user)', { inputs: { user: ClassInstance(new User('ada'), ClassType(User)) } });","handlingStrategy":"validation","validationCode":"function isMontyConvertible(v, seen = new Set()) {\n  if (v === null || ['string', 'number', 'boolean'].includes(typeof v)) return true;\n  if (typeof v !== 'object') return false;\n  if (seen.has(v)) return true;\n  seen.add(v);\n  const proto = Object.getPrototypeOf(v);\n  if (proto !== Object.prototype && proto !== Array.prototype) return false;\n  return Object.values(v).every((x) => isMontyConvertible(x, seen));\n}\nif (!isMontyConvertible(inputs)) console.error('non-plain object in inputs; wrap with ClassInstance or convert');","typeGuard":"function isPlainObjectOrArray(v) {\n  if (v === null || typeof v !== 'object') return false;\n  const p = Object.getPrototypeOf(v);\n  return p === Object.prototype || p === Array.prototype;\n}","tryCatchPattern":"try {\n  await session.feedRun(code, { inputs });\n} catch (e) {\n  const m = e instanceof TypeError ? /Cannot convert (\\S+) instance/.exec(e.message) : null;\n  if (m) throw new Error(`${m[1]} must be wrapped in ClassInstance(...) or converted to a plain value`);\n  throw e;\n}","preventionTips":["Only send plain objects, arrays and primitives unless the value is deliberately wrapped in ClassInstance(...)","Convert Date/Map/Set to plain representations at the boundary","Register classes once and always wrap instances through a shared helper"],"tags":["typescript","serialization","class-instance","type-mismatch"],"backgroundTag":"incompatible-source-type","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}