pydantic/monty · error · TypeError

Cannot convert ${constructorName(value)} instance to a Monty

Error message

Cannot convert ${constructorName(value)} instance to a Monty value — wrap it in ClassInstance(...)

What it means

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.

Source

Thrown at crates/monty-js/ts/classInstance.ts:553

  if (marker === 'ClassInstance') {
    // Identity-bearing markers are produced internally by this walk, never
    // held by host code (`restore` maps them to the original object or a
    // MontyClassProxy). One arriving here is forged — e.g. embedded
    // in attacker-controlled JSON to impersonate a registered instance.
    throw new TypeError('raw ClassInstance markers are not accepted — wrap the object in ClassInstance(...)')
  }
  if (marker === 'Type' && (value as { classType?: unknown }).classType !== undefined) {
    // Same reasoning for a host-class marker; builtin `Type` markers
    // (`{ value: 'int' }`) carry no identity and pass through.
    throw new TypeError('raw Type markers are not accepted — pass the class through ClassType(...)')
  }
  if (marker !== undefined) {
    return value
  }
  if (isPlainObject(value)) {
    return walkPlainObject(value as Record<string, unknown>, walk)
  }
  throw new TypeError(
    `Cannot convert ${constructorName(value)} instance to a Monty value — wrap it in ClassInstance(...)`,
  )
}

/**
 * Inbound walk over a sandbox value reaching the host: maps `ClassInstance`
 * markers to the original wrapped object when the id is in `store` (identity
 * preserved), else to a [`MontyClassProxy`] proxy with recursively
 * restored attrs; maps a host-class `Type` marker to the registered class
 * object the same way (an unregistered class stays a marker); recurses into
 * containers. Wire values are already depth-bounded by the native layer, so
 * no guard is needed here.
 */
export function restore(value: unknown, store: InstanceStore): unknown {
  if (typeof value !== 'object' || value === null) {
    return value
  }
  const walk = (item: unknown) => restore(item, store)

View on GitHub (pinned to adc986b362)

Solutions

  1. Wrap the instance: ClassInstance(instance, ClassType(SomeClass))
  2. Convert the value to a plain object/array/primitive before passing it
  3. Register the class once via the class-registration API and consistently wrap every instance you send

Example fix

// before
session.feedRun('describe(user)', { inputs: { user: new User('ada') } });
// after
session.feedRun('describe(user)', { inputs: { user: ClassInstance(new User('ada'), ClassType(User)) } });
Defensive patterns

Strategy: validation

Validate before calling

function isMontyConvertible(v, seen = new Set()) {
  if (v === null || ['string', 'number', 'boolean'].includes(typeof v)) return true;
  if (typeof v !== 'object') return false;
  if (seen.has(v)) return true;
  seen.add(v);
  const proto = Object.getPrototypeOf(v);
  if (proto !== Object.prototype && proto !== Array.prototype) return false;
  return Object.values(v).every((x) => isMontyConvertible(x, seen));
}
if (!isMontyConvertible(inputs)) console.error('non-plain object in inputs; wrap with ClassInstance or convert');

Type guard

function isPlainObjectOrArray(v) {
  if (v === null || typeof v !== 'object') return false;
  const p = Object.getPrototypeOf(v);
  return p === Object.prototype || p === Array.prototype;
}

Try / catch

try {
  await session.feedRun(code, { inputs });
} catch (e) {
  const m = e instanceof TypeError ? /Cannot convert (\S+) instance/.exec(e.message) : null;
  if (m) throw new Error(`${m[1]} must be wrapped in ClassInstance(...) or converted to a plain value`);
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/8f1c3e8be3ccfcb1. Report an issue: GitHub.