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
- Only list names in `allowedMethods` that are actually functions on the instance (or its prototype)
- Use an explicit list rather than `'all'` for data-like objects so non-function fields are never callable
- Move non-function values to `eagerAttrs`/`lazyAttrs` instead of `allowedMethods`
- 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
- Derive `allowedMethods` by filtering on `typeof obj[name] === 'function'`
- Prefer explicit method lists over `'all'` for data-heavy objects
- Put non-function fields in `eagerAttrs`, not `allowedMethods`
- When refactoring a class method into a field/getter, update every wrapper policy that names it
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
- ClassInstance expects an object instance
- ClassInstance expects an instance of a class, not a null-pro
- classType does not match the instance's class
- pass name on the ClassType wrapper, not alongside classType
- ClassType expects a class (constructor function)
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/b7d32679270c626f.
Report an issue: GitHub.