{"record":{"id":"b40fc382cdb01412","repo":"siyuan-note/siyuan","slug":"task-executor-panicked-v","errorCode":null,"errorMessage":"task executor panicked: %v","messagePattern":"task executor panicked: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/worker.go","lineNumber":64,"sourceCode":"\t\treturn fmt.Errorf(\"worker event loop not initialized\")\n\t}\n\n\tsuccess := w.loop.RunOnLoop(func(rt *goja.Runtime) {\n\t\tvar result any\n\t\tvar err error\n\n\t\tdefer func() {\n\t\t\tdefer func() {\n\t\t\t\t// 捕获回调中的 panic 并保留原始调用栈，便于定位 Promise 处理异常。\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tlogging.LogErrorf(\"task callback panicked: %v\\n%s\", r, debug.Stack())\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// 捕获执行器中的 panic 并保留原始调用栈，同时将错误传给回调。\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tlogging.LogErrorf(\"task executor panicked: %v\\n%s\", r, debug.Stack())\n\t\t\t\terr = fmt.Errorf(\"task executor panicked: %v\", r)\n\t\t\t}\n\t\t\tif callback != nil {\n\t\t\t\tcallback(rt, result, err)\n\t\t\t}\n\t\t}()\n\n\t\tresult, err = executor(rt)\n\t})\n\tif !success {\n\t\treturn fmt.Errorf(\"failed to run task on event loop\")\n\t}\n\treturn nil\n}\n\nfunc (w *Worker) RunSync(fn TaskExecutor) (result any, err error) {\n\tresponse := make(chan TaskResult, 1)\n\terr = w.Run(fn, func(rt *goja.Runtime, result any, err error) {\n\t\tresponse <- TaskResult{result, err}","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/plugin/worker.go#L46-L82","documentation":"Worker.Run wraps task execution on the JS event loop with a recover() so a panic inside a TaskExecutor doesn't crash the kernel. When a panic is caught, the original stack is logged and the task fails with this wrapped error, which is then delivered to the TaskCallback. The message includes the panic value; the real stack is in the kernel log.","triggerScenarios":"Any TaskExecutor passed to worker.Run panics — e.g. nil pointer dereference, index out of range, or a type assertion failure while building JS arguments or processing results on the goja runtime.","commonSituations":"A plugin returns data in an unexpected shape and the executor's type assertion (`x.(*SomeType)`) panics; a nil map/slice access in the executor; a bug introduced by a kernel update in argument conversion code.","solutions":["Read the full stack trace logged by logging.LogErrorf alongside 'task executor panicked' to locate the panicking line.","Fix the underlying panic in the TaskExecutor (nil check, safe type assertion with ok, bounds check).","Handle the error in the TaskCallback so callers receive a proper failure instead of hanging.","Replace unchecked type assertions in the executor with the comma-ok form to convert panics into errors."],"exampleFix":"// before\nv := arg.(*MyType)\n// after\nv, ok := arg.(*MyType)\nif !ok {\n    err = fmt.Errorf(\"unexpected argument type %T\", arg)\n    return\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"func(rt *goja.Runtime) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"task executor panicked: %v\", r)\n        }\n    }()\n    // executor body\n}","preventionTips":["Use comma-ok type assertions instead of unchecked assertions in executors.","Nil-check maps/slices/pointers before use inside task executors.","Always implement TaskCallback error handling so panics surface to callers.","Keep the kernel log nearby — the full stack is logged, not returned."],"tags":["panic","recovery","goja","worker"],"backgroundTag":"internal-invariant-violation","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}