{"record":{"id":"b7d32679270c626f","repo":"pydantic/monty","slug":"notcallablemessage-method","errorCode":null,"errorMessage":"notCallableMessage(method)","messagePattern":"notCallableMessage\\(method\\)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/monty-js/ts/classInstance.ts","lineNumber":148,"sourceCode":"   * `__call__` is always rejected on instances — only [`ClassType`] accepts\n   * it (as construction) — so even `allowedMethods: 'all'` cannot invoke the\n   * instance itself.\n   */\n  callMethod(name: string, args: unknown[], kwargs: Record<string, unknown>): unknown {\n    const policy = this.options.allowedMethods\n    if (name === '__call__' || !policyAllows(policy, name)) {\n      throw this.attrError(name)\n    }\n    const owner = findMemberOwner(this.instance, name)\n    if (owner === undefined) {\n      throw this.attrError(name)\n    }\n    const method = (this.instance as Record<string, unknown>)[name]\n    if (policy === 'all' && !this.isMethodUnderAll(owner, method)) {\n      throw this.attrError(name)\n    }\n    if (typeof method !== 'function') {\n      throw new TypeError(notCallableMessage(method))\n    }\n    const callArgs = Object.keys(kwargs).length > 0 ? [...args, kwargs] : args\n    const result = method.apply(this.instance, callArgs)\n    return isThenable(result)\n      ? Promise.resolve(result).then((value) => this.convertValue(name, value))\n      : this.convertValue(name, result)\n  }\n\n  /**\n   * Transforms one value crossing to the sandbox (see\n   * [`ClassInstanceOptions.convertValue`]). The default passes values through\n   * unchanged — deliberately no automatic wrapping: each object's exposure\n   * must be an explicit host decision, since a wrapper inheriting this\n   * wrapper's policies could silently widen access to an instance the host\n   * had locked down elsewhere.\n   */\n  convertValue(name: string, value: unknown): unknown {\n    if (this.options.convertValue !== undefined) {","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-js/ts/classInstance.ts#L130-L166","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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'`"],"exampleFix":"// before\nnew ClassInstance(config, { allowedMethods: 'all' }) // config.timeout is a number\n// after\nconst methods = ['reset', 'validate'].filter((n) => typeof config[n] === 'function')\nnew ClassInstance(config, { allowedMethods: methods, eagerAttrs: 'all' })","handlingStrategy":"validation","validationCode":"function callableMethods(obj: object, names: readonly string[]): string[] {\n  return names.filter((n) => typeof (obj as Record<string, unknown>)[n] === 'function')\n}\nconst wrapper = new ClassInstance(obj, { allowedMethods: callableMethods(obj, ['run', 'count']) })","typeGuard":"const isCallable = (obj: object, name: string): boolean =>\n  typeof (obj as Record<string, unknown>)[name] === 'function'","tryCatchPattern":"try {\n  result = await session.feedRun(code, { inputs: { obj: wrapper } })\n} catch (err) {\n  if (err instanceof TypeError && /not callable|not a function/i.test(err.message)) {\n    // the exposed name resolved to a non-function: fix the policy host-side\n    console.error('allowedMethods lists a non-function property')\n  }\n  throw err\n}","preventionTips":["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"],"tags":["typescript","policy","host-binding"],"backgroundTag":"type-mismatch","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"}