{"record":{"id":"6908244c3af3021b","repo":"siyuan-note/siyuan","slug":"third-argument-must-be-a-handler-function","errorCode":null,"errorMessage":"third argument must be a handler function","messagePattern":"third argument must be a handler function","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/api_agent.go","lineNumber":131,"sourceCode":"\t\t\t\t\t\tif effectsValue := configObj.Get(\"effects\"); isJsValueNotNull(effectsValue) {\n\t\t\t\t\t\t\tif effects, err = jsCapabilityEffectsToGoEffects(rt, effectsValue); err != nil {\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif actionEffectsValue := configObj.Get(\"actionEffects\"); isJsValueNotNull(actionEffectsValue) {\n\t\t\t\t\t\t\tif actionEffects, err = jsCapabilityActionEffectsToGoEffects(rt, actionEffectsValue); err != nil {\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"second argument must be a config object\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif fn, ok := goja.AssertFunction(call.Argument(2)); ok {\n\t\t\t\t\thandler = fn\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"third argument must be a handler function\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\trunErr := p.worker.Run(func(rt *goja.Runtime) (result any, err error) {\n\t\t\tif argErr != nil {\n\t\t\t\terr = argErr\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfullToolName := pluginCapabilityModelName(p.Name, name)\n\t\t\tdeclaredActionEffects := make(map[string]tools.ToolEffects, len(actionEffects)+1)\n\t\t\tfor action, actionEffect := range actionEffects {\n\t\t\t\tdeclaredActionEffects[action] = actionEffect\n\t\t\t}\n\t\t\tif effects != nil {","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/api_agent.go#L113-L149","documentation":"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).","triggerScenarios":"registerCapability(name, config, \"handler\"), passing a non-function value like an object or undefined, or forgetting the third argument entirely so it is undefined.","commonSituations":"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.","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)"],"exampleFix":"// before\nplugin.registerCapability(\"search\", config, doSearch(\"query\"));\n// after\nplugin.registerCapability(\"search\", config, async (args) => doSearch(args.query));","handlingStrategy":"type-guard","validationCode":"if (typeof handler !== \"function\") { throw new TypeError(\"handler must be a function, got \" + typeof handler); }","typeGuard":"const isCallable = (v) => typeof v === \"function\";","tryCatchPattern":"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; }","preventionTips":["Pass the function reference, never an invoked result","Bind class methods before registering","Verify handler names resolve (no typos) before the call"],"tags":["plugin-api","type-error","callback"],"backgroundTag":"invalid-argument-value","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}