siyuan-note/siyuan · error

third argument must be a handler function

Error message

third argument must be a handler function

What it means

The third argument to registerCapability() was not a callable function. The runtime uses goja.AssertFunction on argument 2 and rejects anything else, because the handler is invoked by the agent runtime to execute the capability and must be a JS function (possibly async).

Solutions

  1. Pass an actual function reference or arrow function as the third argument
  2. Do not invoke the handler when passing it (remove trailing ())
  3. If using a class method, bind it: handler.bind(instance)

Example fix

// before
plugin.registerCapability("search", config, doSearch("query"));
// after
plugin.registerCapability("search", config, async (args) => doSearch(args.query));
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof handler !== "function") { throw new TypeError("handler must be a function, got " + typeof handler); }

Type guard

const isCallable = (v) => typeof v === "function";

Try / catch

try { plugin.registerCapability(name, config, handler); } catch (e) { if (String(e).includes("third argument must be a handler function")) { throw new TypeError("Pass the function itself, do not call it: pass handler, not handler()"); } throw e; }

Prevention

When it happens

Trigger: registerCapability(name, config, "handler"), passing a non-function value like an object or undefined, or forgetting the third argument entirely so it is undefined.

Common situations: Passing a promise/result instead of a function (registerCapability(name, config, doWork()) vs doWork); typos where the function name is undefined at call time; passing a class instead of an instance method bound function.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/6908244c3af3021b. Report an issue: GitHub.

Appendix: source

Thrown at kernel/plugin/api_agent.go:131

						if effectsValue := configObj.Get("effects"); isJsValueNotNull(effectsValue) {
							if effects, err = jsCapabilityEffectsToGoEffects(rt, effectsValue); err != nil {
								return
							}
						}
						if actionEffectsValue := configObj.Get("actionEffects"); isJsValueNotNull(actionEffectsValue) {
							if actionEffects, err = jsCapabilityActionEffectsToGoEffects(rt, actionEffectsValue); err != nil {
								return
							}
						}
					}
				} else {
					err = fmt.Errorf("second argument must be a config object")
					return
				}
				if fn, ok := goja.AssertFunction(call.Argument(2)); ok {
					handler = fn
				} else {
					err = fmt.Errorf("third argument must be a handler function")
					return
				}
				return
			}
		}()

		runErr := p.worker.Run(func(rt *goja.Runtime) (result any, err error) {
			if argErr != nil {
				err = argErr
				return
			}

			fullToolName := pluginCapabilityModelName(p.Name, name)
			declaredActionEffects := make(map[string]tools.ToolEffects, len(actionEffects)+1)
			for action, actionEffect := range actionEffects {
				declaredActionEffects[action] = actionEffect
			}
			if effects != nil {

View on GitHub (pinned to 9f775e8a12)