{"record":{"id":"0e7fc25c197f7b70","repo":"siyuan-note/siyuan","slug":"failed-to-remove-storage-path-from-watcher-v","errorCode":null,"errorMessage":"failed to remove storage path from watcher: %v","messagePattern":"failed to remove storage path from watcher: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/api_storage.go","lineNumber":120,"sourceCode":"\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 removeErr := p.removeStorageWatch(abs); removeErr != nil {\n\t\t\t\terr = fmt.Errorf(\"failed to remove storage path from watcher: %v\", removeErr)\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) {\n\t\t\t\tif resolveErr := resolve(result); resolveErr != nil {\n\t\t\t\t\tlogging.LogErrorf(\"[plugin:%s] siyuan.storage.watcher.remove resolve: %v\", p.Name, resolveErr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif rejectErr := reject(rt.NewGoError(err)); rejectErr != nil {\n\t\t\t\t\tlogging.LogErrorf(\"[plugin:%s] siyuan.storage.watcher.remove reject: %v\", p.Name, rejectErr)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\tif runErr != nil {\n\t\t\tlogging.LogErrorf(\"[plugin:%s] siyuan.storage.watcher.remove worker run: %v\", p.Name, runErr)\n\t\t\tif rejectErr := reject(rt.NewGoError(runErr)); rejectErr != nil {\n\t\t\t\tlogging.LogErrorf(\"[plugin:%s] siyuan.storage.watcher.remove reject: %v\", p.Name, rejectErr)\n\t\t\t}","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/api_storage.go#L102-L138","documentation":"Wraps an error returned by the plugin's internal storage watcher when removeStorageWatch fails while servicing siyuan.storage.watcher.remove(path). The wrapped value (%v) is the underlying watcher error, typically a failure deregistering the path from the filesystem watcher (fsnotify) or an internal state inconsistency such as the watch entry not being removable.","triggerScenarios":"Calling siyuan.storage.watcher.remove(path) with a path that resolves fine but whose watcher entry cannot be removed internally; concurrent add/remove races on the same path; watcher subsystem already closed or in a bad state.","commonSituations":"Plugin unloading code calling watcher.remove during teardown after the watcher was already disposed; removing the same path twice concurrently; watch was added through a different resolved path (e.g. different casing or './' prefix) than the one being removed.","solutions":["Read the wrapped inner error (%v) in the rejected Promise to identify the watcher-level cause","Ensure the path string passed to remove exactly matches the one passed to add (same relative form)","Avoid calling watcher.remove concurrently or twice for the same path; serialize the calls","If it occurs at plugin unload, skip removal errors — the watcher is disposed with the plugin anyway"],"exampleFix":"// before\nawait siyuan.storage.watcher.remove(\"./data/state.json\"); // added as \"data/state.json\"\n// after\nawait siyuan.storage.watcher.remove(\"data/state.json\"); // same string as watcher.add","handlingStrategy":"try-catch","validationCode":"if (typeof path === \"string\" && path === lastAddedPath) { /* safe to remove */ }","typeGuard":"const matchesAdded = (p) => typeof p === \"string\" && p === addedWatchPath;","tryCatchPattern":"try { await siyuan.storage.watcher.remove(path); } catch (e) { log(\"watcher remove failed:\", e.message); /* non-fatal */ }","preventionTips":["Track added watch paths in a Set and remove only known entries","Use the identical path string for add and remove (no ./ prefixes or case changes)","Treat remove errors as non-fatal during plugin unload — the watcher is disposed anyway","Avoid concurrent add/remove on the same path"],"tags":["plugins","filesystem","watcher","siyuan"],"backgroundTag":"file-watch-remove-failed","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"}