{"record":{"id":"dd8da81a7bbfe366","repo":"siyuan-note/siyuan","slug":"siyuan-server-s-s-handler-is-not-a-function","errorCode":null,"errorMessage":"siyuan.server[%s][%s].handler is not a function","messagePattern":"siyuan\\.server\\[(.+?)\\]\\[(.+?)\\]\\.handler is not a function","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/sandbox.go","lineNumber":517,"sourceCode":"\t\treturn\n\t}\n\n\thandlerObj = handlerObjValue.ToObject(rt)\n\tif handlerObj == nil {\n\t\terr = fmt.Errorf(\"globalThis.siyuan.server[%s][%s] is not an object\", scope, requestType)\n\t\treturn\n\t}\n\n\t// Get handler: siyuan.server[scope][requestType].handler\n\thandlerValue := handlerObj.Get(\"handler\")\n\tif !isJsValueNotNull(handlerValue) {\n\t\terr = fmt.Errorf(\"siyuan.server[%s][%s].handler is not set\", scope, requestType)\n\t\treturn\n\t}\n\n\thandler, ok := goja.AssertFunction(handlerValue)\n\tif !ok {\n\t\terr = fmt.Errorf(\"siyuan.server[%s][%s].handler is not a function\", scope, requestType)\n\t\treturn\n\t}\n\n\treturn\n}\n\n// requestGoToJs converts a Go Request to a JavaScript value.\nfunc requestGoToJs(p *KernelPlugin, rt *goja.Runtime, request *Request) (jsRequest goja.Value, err error) {\n\t// convert body raw data to js object\n\tif data, ok := request.Request.Body.Data.(*[]byte); ok && data != nil {\n\t\trequest.Request.Body.Data, err = NewDataObject(p, rt, *data)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t// convert body form files data to js object\n\tif request.Request.Body.Form != nil {","sourceCodeStart":499,"sourceCodeEnd":535,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/sandbox.go#L499-L535","documentation":"Thrown by getRequestHandler when globalThis.siyuan.server[scope][requestType].handler exists and is non-null but goja.AssertFunction fails — the handler is present but not a JS function (e.g. a string, object, or number). The handler callable contract is violated.","triggerScenarios":"Plugin assigns handler to a non-function value: server.network.beforeRequest = { handler: 'doStuff' } or { handler: 42 } or { handler: {} }.","commonSituations":"Plugin author stored a config/label in handler by mistake; refactor renamed a function and left a stale non-function reference; serialization stored a string identifier instead of the function.","solutions":["Assign an actual function to the handler property.","After refactor, confirm the referenced symbol is still a function and not shadowed by a string/config.","Add a typeof === 'function' guard in the plugin before registration."],"exampleFix":"// before\nglobalThis.siyuan.server.network.beforeRequest = { handler: 'onBeforeRequest' }\n// after\nglobalThis.siyuan.server.network.beforeRequest = { handler: (req) => { /*...*/ } }","handlingStrategy":"type-guard","validationCode":"// Plugin side: ensure handler is a function before assigning.\nfunction setHandler(scope: string, rt: string, fn: unknown): void {\n  if (typeof fn !== 'function') throw new Error('handler must be a function')\n  globalThis.siyuan.server[scope][rt] = { handler: fn }\n}","typeGuard":"function isCallable(v: unknown): v is (...a: any[]) => any {\n  return typeof v === 'function'\n}","tryCatchPattern":null,"preventionTips":["Assign only functions to handler; never strings/numbers/objects.","After refactors, confirm the referenced symbol is still a function.","Add typeof === 'function' guards in the registration helper."],"tags":["plugin","goja","javascript-runtime","server-hooks","type-mismatch"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}