pydantic/monty · error · TypeError

notCallableMessage(method)

Error message

notCallableMessage(method)

What it means

Raised by `BaseWrapper.callMethod` when the sandbox calls a method that the wrapper's `allowedMethods` policy permits by name, but the property on the host instance does not hold a function at call time (it holds a plain value, object, or is otherwise non-callable). The message comes from `notCallableMessage(method)` in `errors.ts` and describes what the property actually contains, so the sandbox sees a `TypeError` mirroring calling a non-function in JS.

Source

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

   * `__call__` is always rejected on instances — only [`ClassType`] accepts
   * it (as construction) — so even `allowedMethods: 'all'` cannot invoke the
   * instance itself.
   */
  callMethod(name: string, args: unknown[], kwargs: Record<string, unknown>): unknown {
    const policy = this.options.allowedMethods
    if (name === '__call__' || !policyAllows(policy, name)) {
      throw this.attrError(name)
    }
    const owner = findMemberOwner(this.instance, name)
    if (owner === undefined) {
      throw this.attrError(name)
    }
    const method = (this.instance as Record<string, unknown>)[name]
    if (policy === 'all' && !this.isMethodUnderAll(owner, method)) {
      throw this.attrError(name)
    }
    if (typeof method !== 'function') {
      throw new TypeError(notCallableMessage(method))
    }
    const callArgs = Object.keys(kwargs).length > 0 ? [...args, kwargs] : args
    const result = method.apply(this.instance, callArgs)
    return isThenable(result)
      ? Promise.resolve(result).then((value) => this.convertValue(name, value))
      : this.convertValue(name, result)
  }

  /**
   * Transforms one value crossing to the sandbox (see
   * [`ClassInstanceOptions.convertValue`]). The default passes values through
   * unchanged — deliberately no automatic wrapping: each object's exposure
   * must be an explicit host decision, since a wrapper inheriting this
   * wrapper's policies could silently widen access to an instance the host
   * had locked down elsewhere.
   */
  convertValue(name: string, value: unknown): unknown {
    if (this.options.convertValue !== undefined) {

View on GitHub (pinned to adc986b362)

Solutions

  1. Only list names in `allowedMethods` that are actually functions on the instance (or its prototype)
  2. Use an explicit list rather than `'all'` for data-like objects so non-function fields are never callable
  3. Move non-function values to `eagerAttrs`/`lazyAttrs` instead of `allowedMethods`
  4. Check the property's type host-side before exposing: `typeof instance[name] === 'function'`

Example fix

// before
new ClassInstance(config, { allowedMethods: 'all' }) // config.timeout is a number
// after
const methods = ['reset', 'validate'].filter((n) => typeof config[n] === 'function')
new ClassInstance(config, { allowedMethods: methods, eagerAttrs: 'all' })
Defensive patterns

Strategy: validation

Validate before calling

function callableMethods(obj: object, names: readonly string[]): string[] {
  return names.filter((n) => typeof (obj as Record<string, unknown>)[n] === 'function')
}
const wrapper = new ClassInstance(obj, { allowedMethods: callableMethods(obj, ['run', 'count']) })

Type guard

const isCallable = (obj: object, name: string): boolean =>
  typeof (obj as Record<string, unknown>)[name] === 'function'

Try / catch

try {
  result = await session.feedRun(code, { inputs: { obj: wrapper } })
} catch (err) {
  if (err instanceof TypeError && /not callable|not a function/i.test(err.message)) {
    // the exposed name resolved to a non-function: fix the policy host-side
    console.error('allowedMethods lists a non-function property')
  }
  throw err
}

Prevention

When it happens

Trigger: Wrapping an object with `allowedMethods: ['count']` where `count` is a number property, not a method; a policy listing a name that resolves to an accessor returning a non-function; `'all'` policy plus an own callable function check passing but the resolved value later replaced by a non-function; policy typo where the same name is an attribute and not a method.

Common situations: Config/data objects (e.g. a settings record with scalar fields) wrapped with permissive `allowedMethods: 'all'`; class refactors that turned a method into a getter or a plain field; sandbox code enumerating and calling everything the host exposed.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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