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
- Pass an actual function reference or arrow function as the third argument
- Do not invoke the handler when passing it (remove trailing ())
- 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
- Pass the function reference, never an invoked result
- Bind class methods before registering
- Verify handler names resolve (no typos) before the call
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
- first argument must be a tool name string
- second argument must be a config object
- capability name must not be empty
- config.description is required and must be a string
- config.description must not be empty
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)