{"record":{"id":"33a942f5eab9a226","repo":"siyuan-note/siyuan","slug":"path-required","errorCode":null,"errorMessage":"path required","messagePattern":"path required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/api_client.go","lineNumber":61,"sourceCode":"\t\t}\n\t}()\n\n\tclient := rt.NewObject()\n\n\tlo.Must0(client.Set(\"fetch\", 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\tmethod := \"GET\"\n\t\theaders := map[string]string{}\n\t\tvar bodyString *string\n\t\tvar bodyBytes *[]byte\n\n\t\tif 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\t\tif argErr == nil && !strings.HasPrefix(path, \"/\") {\n\t\t\targErr = fmt.Errorf(\"path must start with /\")\n\t\t}\n\t\tif argErr == nil {\n\t\t\tif init := call.Argument(1); isJsValueNotNull(init) {\n\t\t\t\tif initObj := init.ToObject(rt); initObj != nil {\n\t\t\t\t\tif m := initObj.Get(\"method\"); goja.IsString(m) {\n\t\t\t\t\t\tmethod = m.String()\n\t\t\t\t\t}\n\n\t\t\t\t\tif h := initObj.Get(\"headers\"); isJsValueNotNull(h) {\n\t\t\t\t\t\tif exportErr := rt.ExportTo(h, &headers); exportErr != nil {\n\t\t\t\t\t\t\targErr = fmt.Errorf(\"failed to export headers: %w\", exportErr)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif argErr == nil {","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/api_client.go#L43-L79","documentation":"Thrown by the siyuan.client.fetch JS API exposed to plugins when the first argument is not a string. The fetch function routes HTTP requests to the kernel's own loopback server (http://127.0.0.1:{port}{path}), and the path must be a string starting with '/'. If goja.IsString returns false on call.Argument(0), the path is considered absent.","triggerScenarios":"Calling siyuan.client.fetch() with no arguments, or with a non-string first argument such as a number, object, or URL instance. Example: siyuan.client.fetch(42) or siyuan.client.fetch({ url: '/api/...' }).","commonSituations":"Plugin developer passes an options object first (like browser fetch's init); uses a URL object instead of a string path; the path variable was never assigned; a refactor changed the call signature.","solutions":["Pass the API path as a plain string starting with '/', e.g., '/api/notebook/lsNotebooks'","If using a URL object, convert to string and extract just the path portion","Ensure the path argument is the first positional parameter"],"exampleFix":"// before\nawait siyuan.client.fetch({ url: '/api/notebook/lsNotebooks' });\n\n// after\nconst resp = await siyuan.client.fetch('/api/notebook/lsNotebooks', {\n  method: 'POST',\n  body: JSON.stringify({})\n});","handlingStrategy":"type-guard","validationCode":"if (typeof path !== 'string' || !path.startsWith('/')) {\n  throw new Error('First argument to siyuan.client.fetch must be a path string starting with /');\n}\nconst resp = await siyuan.client.fetch(path, init);","typeGuard":"function isFetchPathArg(v) {\n  return typeof v === 'string' && v.startsWith('/');\n}","tryCatchPattern":"try {\n  const resp = await siyuan.client.fetch(path, { method: 'POST', body: JSON.stringify(payload) });\n} catch (e) {\n  console.error('siyuan.client.fetch failed:', e.message);\n}","preventionTips":["Always pass the API path as the first string argument starting with '/'","This fetch is NOT the browser fetch API — path comes first, then the init object","Do not pass a URL object or options object as the first argument"],"tags":["plugin","client","fetch","type-mismatch","javascript"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}