pydantic/monty · error · TypeError

ClassInstance expects an object instance

Error message

ClassInstance expects an object instance

What it means

A `TypeError` from the `BaseWrapper` constructor: `ClassInstance` (and `ClassType` via the shared base) only wraps host objects or functions, and the value passed as `instance` was neither — typically a primitive (`string`, `number`, `boolean`), `null`, or `undefined`. The wrapper must be able to store and route calls to a real object, so non-object values are rejected at construction time rather than failing later mid-session.

Source

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

  /** A [`ClassType`] wrapper for the instance's class, overriding the default
   *  one materialized from the constructor — pass one to grant class-level
   *  policies (or a pinned class id) alongside the instance. Must wrap the
   *  instance's own constructor. Its eager class attrs are sent with every
   *  crossing of the instance, so `type(x)` in the sandbox sees them. */
  classType?: ClassType
}

/** Shared behavior of [`ClassInstance`] and [`ClassType`]: the wrapped value,
 *  the attr/method exposure policies, and the dispatch entry points the
 *  session layer calls (`getEagerAttrs`, `lookupLazyAttr`, `callMethod`). */
export abstract class BaseWrapper {
  constructor(
    /** The wrapped host object; returned unchanged when the sandbox returns the instance. */
    readonly instance: object,
    readonly options: BaseWrapperOptions = {},
  ) {
    if ((typeof instance !== 'object' && typeof instance !== 'function') || instance === null) {
      throw new TypeError('ClassInstance expects an object instance')
    }
    validatePolicy('eagerAttrs', options.eagerAttrs)
    validatePolicy('lazyAttrs', options.lazyAttrs)
    validatePolicy('allowedMethods', options.allowedMethods)
  }

  /** Class name shown to the sandbox: `options.name`, else the constructor name. */
  getName(): string {
    if (this.options.name !== undefined) {
      return this.options.name
    }
    return constructorName(this.instance)
  }

  /** The `[name, value]` attr pairs sent into the sandbox with the instance,
   *  each value already passed through `convertValue`. */
  getEagerAttrs(): Array<[string, unknown]> {
    const policy = this.options.eagerAttrs

View on GitHub (pinned to adc986b362)

Solutions

  1. Pass the actual host object, not a primitive or its id
  2. Guard optional values before wrapping: if the value can be null/undefined, skip wrapping or throw your own clearer error
  3. If you only need a plain value in the sandbox, pass it directly as an input — do not wrap primitives in `ClassInstance`

Example fix

// before
const wrapper = user === undefined ? new ClassInstance(undefined) : new ClassInstance(user)
// after
if (user == null || typeof user !== 'object') throw new Error('expected a user object')
const wrapper = new ClassInstance(user, { eagerAttrs: 'all' })
Defensive patterns

Strategy: type-guard

Validate before calling

function assertWrappable(v: unknown): asserts v is object {
  if (v === null || (typeof v !== 'object' && typeof v !== 'function')) {
    throw new TypeError(`ClassInstance needs an object, got ${v === null ? 'null' : typeof v}`)
  }
}
assertWrappable(candidate)
const wrapper = new ClassInstance(candidate)

Type guard

const isWrappable = (v: unknown): v is object =>
  v !== null && (typeof v === 'object' || typeof v === 'function')

Try / catch

try {
  return new ClassInstance(candidate as object, opts)
} catch (err) {
  if (err instanceof TypeError && err.message.includes('expects an object instance')) {
    // pass the raw value through instead of wrapping
    return candidate
  }
  throw err
}

Prevention

When it happens

Trigger: `new ClassInstance(42)`, `new ClassInstance(null)`, `new ClassInstance(undefined)`, or passing a primitive returned by another API (e.g. `JSON.parse` output that is a number/string) where a class instance was expected. Note `typeof x === 'function'` is accepted, so plain functions and classes pass this check.

Common situations: Refactoring that changed a variable from an object to an id; optional values left `undefined`; deserialized data whose shape drifted; calling `new ClassInstance(obj.id)` instead of `new ClassInstance(obj)`.

Related errors


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