{"record":{"id":"222df829404f838b","repo":"siyuan-note/siyuan","slug":"path-required-222df8","errorCode":null,"errorMessage":"path required","messagePattern":"path required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/api_storage.go","lineNumber":59,"sourceCode":"\t\tabs = filepath.Join(p.storageDir, filepath.Clean(relPath))\n\t\tif !(abs == p.storageDir || strings.HasPrefix(abs, p.storageDir+string(filepath.Separator))) {\n\t\t\terr = fmt.Errorf(\"siyuan.storage: path traversal not allowed\")\n\t\t}\n\t\treturn\n\t}\n\n\twatcher := rt.NewObject()\n\n\t// siyuan.storage.watcher.add(path) -> Promise<void>\n\tlo.Must0(watcher.Set(\"add\", rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) goja.Value {\n\t\tpromise, resolve, reject := rt.NewPromise()\n\n\t\tvar argErr error\n\t\tvar path string\n\t\tif len(call.Arguments) >= 1 && goja.IsString(call.Argument(0)) {\n\t\t\tpath = call.Argument(0).String()\n\t\t} else {\n\t\t\targErr = fmt.Errorf(\"path required\")\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\t\t\tabs, resolveErr := resolvePath(path)\n\t\t\tif resolveErr != nil {\n\t\t\t\terr = resolveErr\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif addErr := p.addStorageWatch(abs); addErr != nil {\n\t\t\t\terr = fmt.Errorf(\"failed to add storage path to watcher: %v\", addErr)\n\t\t\t}\n\t\t\treturn\n\t\t}, func(rt *goja.Runtime, result any, err error) {\n\t\t\tif lo.IsNil(err) {","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/api_storage.go#L41-L77","documentation":"Thrown by siyuan.storage.watcher.add() when no string path argument is supplied. watcher.add registers a path under the plugin storage dir with the kernel file watcher so the plugin receives change notifications. It requires at least one argument that is a string; missing or non-string arguments are rejected up front.","triggerScenarios":"Calling watcher.add() with no arguments, watcher.add(undefined), or watcher.add(123). The check is len(call.Arguments) >= 1 && goja.IsString(call.Argument(0)).","commonSituations":"Plugin derives the watch path from a variable that is sometimes undefined (e.g. a config field not yet loaded), or calls add in a loop where one entry is empty.","solutions":["Pass a non-empty relative path string that lives under the storage dir.","Default optional path variables to a sensible relative string before calling add.","Skip the add call when the resolved path is empty or not a string."],"exampleFix":"// before\nawait siyuan.storage.watcher.add(maybePath);\n// after\nif (typeof maybePath === 'string' && maybePath) {\n  await siyuan.storage.watcher.add(maybePath);\n}","handlingStrategy":"validation","validationCode":"if (typeof path !== 'string' || path.length === 0) {\n  throw new TypeError('watcher.add requires a non-empty path string');\n}","typeGuard":"const isStoragePath = (p) => typeof p === 'string' && p.length > 0;","tryCatchPattern":"try { await siyuan.storage.watcher.add(path); }\ncatch (e) { if (/path required/.test(String(e))) { /* skip watch */ } else throw e; }","preventionTips":["Default optional path variables to a concrete relative string.","Skip add when the resolved path is empty or not a string."],"tags":["plugin-api","storage","watcher","argument-validation","javascript"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}