siyuan-note/siyuan · error

path required

Error message

path required

What it means

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.

Source

Thrown at kernel/plugin/api_storage.go:59

		abs = filepath.Join(p.storageDir, filepath.Clean(relPath))
		if !(abs == p.storageDir || strings.HasPrefix(abs, p.storageDir+string(filepath.Separator))) {
			err = fmt.Errorf("siyuan.storage: path traversal not allowed")
		}
		return
	}

	watcher := rt.NewObject()

	// siyuan.storage.watcher.add(path) -> Promise<void>
	lo.Must0(watcher.Set("add", rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
		promise, resolve, reject := rt.NewPromise()

		var argErr error
		var path string
		if len(call.Arguments) >= 1 && goja.IsString(call.Argument(0)) {
			path = call.Argument(0).String()
		} else {
			argErr = fmt.Errorf("path required")
		}

		runErr := p.worker.Run(func(rt *goja.Runtime) (result any, err error) {
			if argErr != nil {
				err = argErr
				return
			}
			abs, resolveErr := resolvePath(path)
			if resolveErr != nil {
				err = resolveErr
				return
			}
			if addErr := p.addStorageWatch(abs); addErr != nil {
				err = fmt.Errorf("failed to add storage path to watcher: %v", addErr)
			}
			return
		}, func(rt *goja.Runtime, result any, err error) {
			if lo.IsNil(err) {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Pass a non-empty relative path string that lives under the storage dir.
  2. Default optional path variables to a sensible relative string before calling add.
  3. Skip the add call when the resolved path is empty or not a string.

Example fix

// before
await siyuan.storage.watcher.add(maybePath);
// after
if (typeof maybePath === 'string' && maybePath) {
  await siyuan.storage.watcher.add(maybePath);
}
Defensive patterns

Strategy: validation

Validate before calling

if (typeof path !== 'string' || path.length === 0) {
  throw new TypeError('watcher.add requires a non-empty path string');
}

Type guard

const isStoragePath = (p) => typeof p === 'string' && p.length > 0;

Try / catch

try { await siyuan.storage.watcher.add(path); }
catch (e) { if (/path required/.test(String(e))) { /* skip watch */ } else throw e; }

Prevention

When it happens

Trigger: 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)).

Common situations: 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.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/222df829404f838b. Report an issue: GitHub.